Android圖文詳解屬性動畫

目錄(?)[+]

Android中的動畫分爲視圖動畫(View Animation)、屬性動畫(Property Animation)以及Drawable動畫。從Android 3.0(API Level 11)開始,Android開始支持屬性動畫,本文主要講解如何使用屬性動畫。關於視圖動畫可以參見博文《Android四大視圖動畫圖文詳解》


概述

視圖動畫侷限比較大,如下所述:

  1. 視圖動畫只能使用在View上面。

  2. 視圖動畫並沒有真正改變View相應的屬性值,這導致了UI效果與實際View狀態存在差異,並導致了一系列怪異行爲,比如在使用了視圖動畫TranslateAnimation的View的UI上對其觸摸,你可能驚訝地發現並沒有觸發觸摸事件。

鑑於視圖動畫以上缺陷,從Android 3.0引入了屬性動畫。屬性動畫具有以下特性:

  1. 屬性動畫應用面更廣,不僅僅應用於View,可以將其應用到任意的對象上面,且該對象不需要具有UI界面。

  2. 當將屬性動畫作用於某個對象時,可以通過調用對象的setXXX方法實際改變對象的值。所以,當將屬性動畫作用於某個View時,View對象對應的屬性值會被改變。

我們看一下屬性動畫時如何工作的。

其實屬性動畫的工作原理並不複雜,假設一個對象有一個屬性x,我們想通過屬性動畫動態更改該值,假設我們想在40ms內將x的值從0漸變到40,那麼如下圖所示: 
這裏寫圖片描述

隨着時間的增長,對應的x值也相應地線性漸變,當動畫完成時,x的值就是我們設置的最終值40。如果x值線性漸變,那麼x的變化速度就是勻速的。其實,我們也可以變速地改變x的值,這會我們可以一開始加速增加x的值,後面減速增加x的值,如下圖所示: 
這裏寫圖片描述 
如上圖所示,在前20ms,x值加速增大,在後20ms,x值增大的速度降低。

其實,每種改變x值速度的方式都叫做時間插值器TimeInterpolator,第一張圖中使用的時間插值器叫做LinearInterpolator,第二張圖中使用的時間插值器叫做AccelerateDecelerateInterpolator。動畫開始後,時間插值器會根據對應的算法計算出某一時刻x的值,然後我們就可以用該計算出的值更新對象中x屬性的值,這就是屬性動畫的基本工作原理。

屬性動畫中主要的類如下圖所示:

這裏寫圖片描述

下面會對上述類分別進行講解。


Animator

屬性動畫主要的類都在android.animation命名空間下,Animator是屬性動畫的基類,其是一個抽象類,該類定義了許多重要的方法,如下所示:

  • setDuration(long duration) 
    通過setDuration方法可以設置動畫總共的持續時間,以毫秒爲單位。

  • start() 
    通過start方法可以啓動動畫,動畫啓動後不一定會立即運行。如果之前通過調用setStartDelay方法設置了動畫延遲時間,那麼會在經過延遲時間之後再運行動畫;如果沒有設置過動畫的延遲時間,那麼動畫在調用了start()方法之後會立即運行。在調用了start()方法之後,動畫的isStarted()方法就會返回true;在動畫真正運行起來之後,動畫的isRunning()方法就會返回true,這時候動畫纔會調用TimeInterpolator纔開始工作計算屬性在某個時刻的值。調用動畫的start()方法所在的線程必須綁定了一個Looper對象,如果沒有綁定就會報錯。當然,UI線程(即主線程)早就默認綁定了一個Looper對象,所以在主線程中我們就無需擔心這個問題。如果我們想在一個View上使用屬性動畫,那麼我們應該保證我們是在UI線程上調用的動畫的start()方法。start()方法運行後會觸發動畫監聽器AnimatorListener的onAnimationStart方法的執行。

  • setStartDelay(long startDelay) 
    可以通過調用setStartDelay方法設置動畫的延遲運行時間,比如調用setStartDelay(1000)意味着動畫在執行了start()方法1秒之後才真正運行,這種情況下,在調用了start()方法之後,isStarted()方法返回true,表示動畫已經啓動了,但是在start()方法調用後1s內,isRunning()方法返回false,表示動畫還未真正運行,比如在start()方法調用後第0.5秒,由於動畫還在延遲階段,所以isRunning()返回false;在start()方法執行1秒之後,isStarted()方法還是返回true,isRunning()方法也返回了true,表示動畫已經真正開始運行了。通過調用getStartDelay()方法可以返回我們設置的動畫延遲啓動時間,默認值是0。

  • setInterpolator(TimeInterpolator value) 
    我們可以通過調用setInterpolator方法改變動畫所使用的時間插值器,由於視圖動畫也需要使用時間插值器,所以我們可以使用android.view.animation命名空間下的一系列插值器,將其與屬性動畫一起工作。通過動畫的getInterpolator方法可以獲取我們設置的時間插值器。

  • setTarget(Object target) 
    可以通過調用動畫的setTarget方法設置其要操作的對象,這樣可以更新該對象的某個屬性值。實際上,該方法對於ValueAnimator作用不大,因爲ValueAnimator不是直接與某個對象打交道的。setTarget方法對於ObjectAnimator作用較大,因爲ObjectAnimator需要綁定某個要操作的對象,下面會詳細介紹。

  • pause() 
    Android中API Level 19在Animator中加入了pause()方法,該方法可以暫停動畫的執行。調用pause()方法的線程必須與調用start()方法的線程是同一個線程。如果動畫還沒有執行start()或者動畫已經結束了,那麼調用pause()方法沒有任何影響,直接被忽略。當執行了pause()方法後,動畫的isPaused()方法會返回true。pause()方法運行後會觸發動畫監聽器AnimatorPauseListener的onAnimationPause方法的執行。

  • resume() 
    如果動畫通過調用pause()方法暫停了,那麼之後可以通過調用resume()方法讓動畫從上次暫停的地方繼續運行。resume()方法也是從API Level 19引入的,並且調用resume()方法的線程必須與調用start()方法的線程是同一個線程。如果動畫沒有處於暫停狀態(即isPaused()返回false),那麼調用resume()方法會被忽略。resume()方法運行後會觸發動畫監聽器AnimatorPauseListener的onAnimationResume方法的執行。

  • end 
    end()方法執行後,動畫會結束運行,直接從當前狀態跳轉到最終的完成狀態,並將屬性值分配成動畫的終止值,並觸發動畫監聽器AnimatorListener的onAnimationEnd方法的執行。

  • cancel() 
    cancel()方法執行後,動畫也會結束運行,但是與調用end方法不同的是,其不會將屬性值分配給動畫的終止值。比如一個動畫在400ms內將對象的x屬性從0漸變爲40,當運行到第200ms時調用了cancel()方法,那麼屬性x的最終值是20,而不是40,這是與調用end()方法不同的,如果在第200ms調用了end()方法,那麼屬性x的最終值是40。調用cancel()方法後,會先觸發AnimatorListener的onAnimationCancel方法的執行,然後觸發onAnimationEnd方法的執行。

  • clone() 
    Animator實現了java.lang.Cloneable接口。Animator的clone()方法會對動畫進行拷貝,但是該方法默認實現的只是淺拷貝,子類可以重寫該方法以實現深拷貝。

  • addListener (Animator.AnimatorListener listener) 
    可以通過addListener方法向Animator添加動畫監聽器,該方法接收的是AnimatorListener接口類型的參數,其具有四個方法:onAnimationStart、onAnimationCancel、onAnimationEnd、onAnimationRepeat。我們上面已經介紹了前三個方法,onAnimationRepeat方法會在動畫在重複播放的時候被回調。Android中的AnimatorListenerAdapter類是個抽象類,其實現了AnimatorListener接口,併爲所有方法提供了一個空實現。

  • addPauseListener (Animator.AnimatorPauseListener listener) 
    可以通過addPauseListener方法可以向Animator添加動畫暫停相關的監聽器,該方法接收的是AnimatorPauseListener接口類型的參數,具有兩個方法:onAnimationPause和onAnimationResume,上面已經提到過,在此不再贅述。AnimatorListenerAdapter同樣也實現了AnimatorPauseListener接口,併爲所有方法提供了一個空實現。


ValueAnimator

ValueAnimator繼承自抽象類Animator。要讓屬性動畫漸變式地更改對象中某個屬性的值,可分兩步操作:第一步,動畫需要計算出某一時刻屬性值應該是多少;第二步,需要將計算出的屬性值賦值給動畫的屬性。ValueAnimator只實現了第一步,也就是說ValueAnimator只負責以動畫的形式不斷計算不同時刻的屬性值,但需要我們開發者自己寫代碼將計算出的值通過對象的setXXX等方法更新對象的屬性值。

ValueAnimator中有兩個比較重要的屬性,一個是TimeInterpolator類型的屬性,另一個是TypeEvaluator類型的屬性。TimeInterpolator指的就是時間插值器,在上面我們已經介紹過,在此不再贅述。TypeEvaluator是什麼呢?TypeEvaluator表示的是ValueAnimator對哪種類型的值進行動畫處理。ValueAnimator提供了四個靜態方法ofFloat()、ofInt()、ofArgb()和ofObject(),通過這四個方法可以對不同種類型的值進行動畫處理,這四個方法對應了四種TypeEvaluator,下面會詳細說明。

  • public static ValueAnimator ofFloat (float… values) 
    ofFloat方法接收一系列的float類型的值,其內部使用了FloatEvaluator。通過該方法ValueAnimator可以對float值進行動畫漸變,其使用方法如下所示:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    ValueAnimator valueAnimator = ValueAnimator.ofFloat(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>f, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500</span>f);
    
        valueAnimator.addUpdateListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ValueAnimator.AnimatorUpdateListener() {
            <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onAnimationUpdate</span>(ValueAnimator animation) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> deltaY = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)animation.getAnimatedValue();
                textView.setTranslationY(deltaY);
            }
        });
    
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//默認duration是300毫秒</span>
        valueAnimator.setDuration(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3000</span>);
        valueAnimator.start();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

    其效果如下所示: 
    這裏寫圖片描述

    我們通過構造函數指定了動畫的起始值爲0,終止值爲500,動畫的默認持續時間是300毫秒,我們通過setDuration()方法設置爲3000毫秒。該動畫會在3秒內,將值從0到500動畫漸變。ValueAnimator提供了一個addUpdateListener方法,可以通過該方法向其添加AnimatorUpdateListener類型的監聽器。AnimatorUpdateListener有一個onAnimationUpdate方法,ValueAnimator會每隔一定時間(默認間隔10ms)計算屬性的值,每當計算的時候就會回調onAnimationUpdate方法。在該方法中,我們通過調用ValueAnimator的getAnimatedValue()方法獲取到當前動畫計算出的屬性值,然後我們將該值傳入textView的setTranslationY()方法中,從而更新了textView的位置,這樣就通過ValueAnimator以動畫的形式移動textView。

  • public static ValueAnimator ofInt (int… values) 
    ofInt方法與ofFloat方法很類似,只不過ofInt方法接收int類型的值,ofInt方法內部使用了IntEvaluator,其具體使用可參考上面ofFloat的使用代碼,在此不再贅述。

  • public static ValueAnimator ofArgb (int… values) 
    從API Level 21開始,ValueAnimator中加入了ofArgb方法,該方法接收一些列代表了顏色的int值,其內部使用了ArgbEvaluator,可以用該方法實現將一個顏色動畫漸變到另一個顏色,我們從中可以不斷獲取中間動畫產生的顏色值。你可能納悶,既然傳入的還是int值,那直接用ofInt方法不就行了嗎,幹嘛還要新增一個ofArgb方法呢?實際上用ofInt方法是不能完成顏色動畫漸變的。我們知道一個int值包含四個字節,在Android中第一個字節代表Alpha分量,第二個字節代表Red分量,第三個字節代表Green分量,第四個字節代表Blue分量,且我們常用16進製表示顏色,這樣看起來更明顯易懂一些,比如int值0xffff0000表示的紅色,0xff00ff00表示的是綠色,最前面的ff表示的是Alpha。ofArgb方法會通過ArgbEvaluator將顏色拆分成四個分量,然後分別對各個分量進行動畫計算,然後將四個計算完的分量再重新組合成一個表示顏色的int值,這就是ofArgb方法的工作原理。使用方法如下所示:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//ValueAnimator.ofArgb()方法是在API Level 21中才加入的</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(Build.VERSION.SDK_INT >= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">21</span>){
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//起始顏色爲紅色</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xffff0000</span>;
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//終止顏色爲綠色</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff00ff00</span>;
            ValueAnimator valueAnimator = ValueAnimator.ofArgb(startColor, endColor);
            valueAnimator.setDuration(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3000</span>);
            valueAnimator.addUpdateListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ValueAnimator.AnimatorUpdateListener() {
                <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onAnimationUpdate</span>(ValueAnimator animation) {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> color = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)animation.getAnimatedValue();
                    textView.setBackgroundColor(color);
                }
            });
            valueAnimator.start();
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li></ul>

    效果如下所示: 
    這裏寫圖片描述

    我們將TextView的顏色通過動畫從紅色漸變到綠色。

  • public static ValueAnimator ofObject (TypeEvaluator evaluator, Object… values) 
    由於我們要進行動畫處理的值是各種各樣的,可能不是float、int或顏色值,那我們怎麼使用屬性動畫呢?爲此,ValueAnimator提供了一個ofObject方法,該方法接收一個TypeEvaluator類型的參數,我們需要實現該接口TypeEvaluator的evaluate方法,只要我們實現了TypeEvaluator接口,我們就能通過ofObject方法處理任意類型的數據。我們之前提到ofArgb方法是從API Level 21才引入的,如果我們想在之前的這之前的版本中使用ofArgb的功能,怎麼辦呢?我們可以擴展TypeEvaluator,從而通過ofObject方法實現ofArgb方法的邏輯,如下所示:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//起始顏色爲紅色</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xffff0000</span>;
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//終止顏色爲綠色</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff00ff00</span>;
        ValueAnimator valueAnimator = ValueAnimator.ofObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> TypeEvaluator() {
            <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Object <span class="hljs-title" style="box-sizing: border-box;">evaluate</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> fraction, Object startValue, Object endValue) {
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//從初始的int類型的顏色值中解析出Alpha、Red、Green、Blue四個分量</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startInt = (Integer) startValue;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startA = (startInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startR = (startInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startG = (startInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startB = startInt & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
    
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//從終止的int類型的顏色值中解析出Alpha、Red、Green、Blue四個分量</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endInt = (Integer) endValue;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endA = (endInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endR = (endInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endG = (endInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endB = endInt & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
    
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//分別對Alpha、Red、Green、Blue四個分量進行計算,</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//最終合成一個完整的int型的顏色值</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startA + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endA - startA))) << <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>) |
                        (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startR + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endR - startR))) << <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span>) |
                        (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startG + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endG - startG))) << <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>) |
                        (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startB + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endB - startB))));
            }
        }, startColor, endColor);
        valueAnimator.setDuration(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3000</span>);
        valueAnimator.addUpdateListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ValueAnimator.AnimatorUpdateListener() {
            <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onAnimationUpdate</span>(ValueAnimator animation) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> color = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)animation.getAnimatedValue();
                textView.setBackgroundColor(color);
            }
        });
        valueAnimator.start();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li></ul>

    以上代碼實現的效果與ofArgb實現的效果是一樣的,都是將TextView從紅色漸變到綠色,就不再附圖了,但是我們可以在API Level 11及以後的版本中都可以使用以上ofObject的代碼,通用性更強。


ObjectAnimator

ObjectAnimator繼承自ValueAnimator。我們之前提到,要讓屬性動畫漸變式地更改對象中某個屬性的值,可分兩步操作:第一步,動畫需要計算出某一時刻屬性值應該是多少;第二步,需要將計算出的屬性值賦值給動畫的屬性。ValueAnimator只實現了第一步,也就是說ValueAnimator只負責以動畫的形式不斷計算不同時刻的屬性值,但需要我們開發者自己寫代碼在動畫監聽器AnimatorUpdateListener的onAnimationUpdate方法中將計算出的值通過對象的setXXX等方法更新對象的屬性值。ObjectAnimator比ValueAnimator更進一步,其會自動調用對象的setXXX方法更新對象中的屬性值。

ObjectAnimator重載了ofFloat()、ofInt()、ofArgb()和ofObject()等靜態方法,我們下面依次說明。

  • ofFloat(Object target, String propertyName, float… values) 
    使用方法如下所示:

    <code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> value1 = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0f</span>;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> value2 = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500f</span>;
        final ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"translationY"</span>, value1, value2);
        objectAnimator.setDuration(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3000</span>);
        objectAnimator.start();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

    以上代碼實現的效果與通過ValueAnimator的ofFloat方法實現的效果相同,此處不再附圖,但是可以看出使用ObjectAnimator代碼更簡潔。在構造函數中,我們將textView作爲target傳遞給ObjectAnimator,然後指定textView要變化的屬性是translationY,最後指定漸變範圍是從0到500。當動畫開始時,ObjectAnimator就會不斷調用textView的setTranslationY方法以更新其值。我們此處演示的是ObjectAnimator與View一起工作,其實ObjectAnimator可以與任意的Object對象工作。如果要更新某個對象中名爲propery的屬性,那麼該Object對象必須具有一個setProperty的setter方法可以讓ObjectAnimator調用。在ofFloat方法最後如果只填入了一個float值,那麼ObjectAnimator需要調用對象的getXXX方法獲取對象初始的屬性值,然後從該初始的屬性值漸變到終止值。

  • ofInt(Object target, String propertyName, int… values) 
    參見ofFloat的使用方法。

  • ofArgb(Object target, String propertyName, int… values) 
    使用代碼如下所示:

    <code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    //ObjectAnimator<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ofArgb</span>()方法是在API Level <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">21</span>中才加入的
        if(Build<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.VERSION</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.SDK</span>_INT >= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">21</span>){
            int startColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xffff0000</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            int endColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff00ff00</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            ObjectAnimator objectAnimator = ObjectAnimator<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ofArgb</span>(textView, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"backgroundColor"</span>, startColor, endColor)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            objectAnimator<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setDuration</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3000</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
            objectAnimator<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.start</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

    效果圖參見ValueAnimator中對應的圖片。

  • ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object… values) 
    使用代碼如下所示:

    <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xffff0000</span>;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endColor = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff00ff00</span>;
        ObjectAnimator objectAnimator = ObjectAnimator.ofObject(textView, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"backgroundColor"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> TypeEvaluator() {
            <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Object <span class="hljs-title" style="box-sizing: border-box;">evaluate</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> fraction, Object startValue, Object endValue) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startInt = (Integer) startValue;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startA = (startInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startR = (startInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startG = (startInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> startB = startInt & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
    
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endInt = (Integer) endValue;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endA = (endInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endR = (endInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endG = (endInt >> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>) & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> endB = endInt & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0xff</span>;
    
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startA + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endA - startA))) << <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>) |
                        (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startR + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endR - startR))) << <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span>) |
                        (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startG + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endG - startG))) << <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>) |
                        (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)((startB + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>)(fraction * (endB - startB))));
            }
        }, startColor, endColor);
        objectAnimator.setDuration(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3000</span>);
        objectAnimator.start();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li></ul>

    效果圖參見ValueAnimator中對應的圖片。


AnimatorSet

AnimatorSet繼承自Animator。AnimatorSet表示的是動畫的集合,我們可以通過AnimatorSet把多個動畫集合在一起,讓其串行或並行執行,從而創造出複雜的動畫效果。

我們想讓TextView先進行旋轉,然後進行平移,最後進行伸縮,我們可以通過AnimatorSet實現該效果,代碼如下所示:

```
    //anim1實現TextView的旋轉動畫
    Animator anim1 = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
    anim1.setDuration(2000);
    //anim2和anim3TextView的平移動畫
    Animator anim2 = ObjectAnimator.ofFloat(textView, "translationX", 0f, 300f);
    anim2.setDuration(3000);
    Animator anim3 = ObjectAnimator.ofFloat(textView, "translationY", 0f, 400f);
    anim3.setDuration(3000);
    //anim4實現TextView的伸縮動畫
    Animator anim4 = ObjectAnimator.ofFloat(textView, "scaleX", 1f, 0.5f);
    anim4.setDuration(2000);


    //第一種方式
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.playSequentially(anim1, anim2, anim4);
    animatorSet.playTogether(anim2, anim3);
    animatorSet.start();

    //第二種方式
    /*AnimatorSet anim23 = new AnimatorSet();
    anim23.playTogether(anim2, anim3);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.playSequentially(anim1, anim23, anim4);
    animatorSet.start();*/

    //第三種方式
    /*AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(anim1).before(anim2);
    animatorSet.play(anim2).with(anim3);
    animatorSet.play(anim4).after(anim2);
    animatorSet.start();*/
```

效果如下所示: 
這裏寫圖片描述

動畫anim1用於旋轉TextView,anim2用於在X軸方向偏移TextView,anim3用於在Y軸方向偏移TextView,anim4用於縮放TextView。我們在以上代碼中提供了三種方式通過AnimationSet把這四個動畫組合到一起,第二種方式和第三種方式被註釋起來了。

其實有很多種辦法實現上述效果,這裏只介紹一下上述三種方式的思路。

  1. 在第一種方式中,調用了animatorSet.playSequentially(anim1, anim2, anim4),該方法將anim1、anim2以及anim4按順序串聯起來放到了animatorSet中,這樣首先會讓動畫anim1執行,anim1執行完成後,會依次執行動畫anim2,執行完anim2之後會執行動畫anim3。通過調用animatorSet.playTogether(anim2, anim3),保證了anim2和anim3同時執行,即動畫anim1完成之後會同時運行anim2和anim3。

  2. 在第二種方式中,我們首先創建了一個AnimatorSet變量anim23,然後通過anim23.playTogether(anim2, anim3)將anim2和anim3組合成一個小的動畫集合。然後我們再把anim1、anim23以及anim4一起傳入到animatorSet.playSequentially(anim1, anim23, anim4)中,這樣anim1、anim23、anim4會依次執行,而anim23中的anim2和anim3會同時執行。該方式同時也演示了可以將一個AnimatorSet作爲動畫的一部分放入另一個AnimatorSet中。

  3. 在第三種方式中,我們使用了AnimatorSet的play方法,該方法返回AnimatorSet.Builder類型,animatorSet.play(anim1).before(anim2)確保了anim1執行完了之後執行anim2,animatorSet.play(anim2).with(anim3)確保了anim2和anim3同時執行,animatorSet.play(anim4).after(anim2)確保了anim2執行完了之後執行anim4。需要說明的是animatorSet.play(anim1).before(anim2)與animatorSet.play(anim2).after(anim1)是完全等價的,之所以在上面代碼中有的寫before,有的寫after,只是爲了讓大家多瞭解一下API。

希望本文對大家學習屬性動畫有所幫助。

發佈了9 篇原創文章 · 獲贊 11 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章