Android動畫深入分析——屬性動畫

屬性動畫是在API11中引入的特性,和View動畫不同,它對作用對象進行了擴展,屬性動畫可以對任何對象做動畫,甚至還可以沒有對象。除了作用對象進行擴展以外,屬性動畫的效果也得到了加強,不再像View動畫那樣只能支持四種簡單的變換。屬性動畫中有ValueAnimator、ObjectAnimator和AnimatorSet等概念,通過它們可以實現絢麗的動畫。

1.使用屬性動畫

屬性動畫可以對任意對象進行動畫而不僅僅是View,動畫默認時間間隔300ms,默認幀率10ms/幀。其可以達到的效果是,在一個時間間隔內完成對象從一個屬性值到另一個屬性值的改變。因此屬性動畫幾乎是無所不能的,只要對象有這個屬性,它都能實現動畫效果。但是屬性動畫從API11纔有,這就嚴重製約了屬性動畫的使用。可以採用開源動畫庫nineoldandroids來兼容以前的版本,採用nineoldandroids,可以在API11以前的系統上使用屬性動畫,nineoldandroids的網址是:http://nineoldandroids.com。
nineoldandroids對屬性動畫做了兼容,在API 11以前的版厄本那其內部是通過代理View動畫來實現的,因此在低Android版本上,它的本質是View動畫,儘管使用方法看起來是屬性動畫。nineoldandroids的功能和系統原始對android.animation.*中類的功能完全一致,使用方法也完全一樣,只要我們用nineoldandroids來編寫動畫,就可以在所有的Android系統上運行。比較常用的幾個動畫類是:ValueAnimator、ObjectAnimator和AnimatorSet,其中ObjectAnimator繼承自ValueAnimatorAnimatorSet是動畫集合,可以定義一組動畫,它們使用起來也是及其簡單的。如何使用屬性動畫呢?下面舉幾個小李子,讀者以看就明白了。
(1)改變一個對象(myObject)的translationY屬性,讓其沿着Y軸向上平移一段距離:它的高度,改動畫在默認時間內完成,動畫完成時間是可以自定義的。想要靈活的效果我們還可以定義插值器和估值算法,但是一般來說我們不需要自定義,系統已經預置了一些,能夠滿足常用的動畫。
        ObjectAnimator.ofFloat(myObject, "translationY", -myObject.getHeight()).start();

(2)改變一個對象的背景色屬性,典型的情形是改變View的背景色,下面的動畫可以讓背景色在3秒內實現從0xFFFF8080到0xFF8080FF的漸變,動畫會無線循環而且會有反轉的效果。
        ValueAnimator colorAnim = ObjectAnimator.ofInt(button, "backgroundColor",0xFFFF8080,0xFF8080FF);
        colorAnim.setDuration(3000);
        colorAnim.setEvaluator(new ArgbEvaluator());
        colorAnim.setRepeatCount(ValueAnimator.INFINITE);
        colorAnim.setRepeatMode(ValueAnimator.REVERSE);
        colorAnim.start();

(3)動畫集合,5秒內對View的旋轉、平移、縮放和透明度都進行了改變。
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(button,"rotationX", 0,360),
                ObjectAnimator.ofFloat(button,"rotationY", 0,180),
                ObjectAnimator.ofFloat(button,"rotation", 0,-90),
                ObjectAnimator.ofFloat(button,"translationX", 0,90),
                ObjectAnimator.ofFloat(button,"translationY", 0,90),
                ObjectAnimator.ofFloat(button,"scaleX", 1,1.5f),
                ObjectAnimator.ofFloat(button,"scaleY", 1,0.5f),
                ObjectAnimator.ofFloat(button,"alpha", 1,0.25f,1)

        );
        set.setDuration(5000).start();
        set.start();

屬性動畫除了通過代碼實現以外,還可以通過XML來定義。屬性動畫需要定義在res/animator/目錄下,它的語法如下所示。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together|sequentially">
    <objectAnimator android:propertyName="string"
        android:duration="int"
        android:valueFrom="float|int|color"
        android:valueTo="float|int|color"
        android:valueType="pathType|floatType|intType|colorType"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode="restart|reverse"/>
    <animator android:duration="int"
        android:valueFrom="float|int|color"
        android:valueTo="float|int|color"
        android:valueType="pathType|floatType|intType|colorType"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode="restart|reverse"/>
    <set>
        ...
    </set>
</set>

屬性動畫的各種參數都比較好理解,在XML中可以定義ValueAnimator、ObjectAnimator以及AnimatorSet,其中<set>標籤對於AnimatorSet,<objectAnimator>標籤則對應ObjectAnimator,<animator>對應ValueAnimator。<set>標籤的android:ordering屬性有兩個可選值:together和sequentially,其中together表示動畫集合中的子動畫同時播放,sequentially則表示動畫集合中的子動畫按照前後順序依次播放,android:ordering屬性默認值是together。
對於<objectAnimator>標籤的各個屬性的含義,下面簡單說明一下,對於<animator>標籤這裏就不再介紹了,因爲它只是比<objectAnimator>少了個android:propertyName屬性而已,其他都是一樣的。
android:propertyName——表述屬性動畫的作用對象的屬性的名稱;
android:duration——表示動畫的時長;

android:valueFrom——表示屬性的起始值;
android:valueTo——表示屬性的結束值;

android:startOffset——表示動畫的延遲時間,當動畫開始後,需要延遲多說毫秒纔會真正播放此動畫;
android:repeatCount——表示動畫的重複次數;

android:repeatMode——表示動畫的重複模式;
android:valueType——表示android:propertyName所指定的屬性的類型,有intType和floatType兩個可選項,分別表示屬性的類型爲整型和浮點型。另外,如果android:propertyName所指定的屬性表示的是顏色,那麼不需要指定android:valueType,系統會自動對顏色類型的屬性做處理。
對於一個動畫來說,有兩個屬性這裏要特殊說明一下,一個是android:repeatCount,它表示動畫循環的次數,默認值爲0,其中-1表示無線循環;另外一個是android:repeatMode,它表示動畫循環的模式,有兩個選項:repeat和reverse,分別表示連續重複和逆向重複。連續重複比較好理解,就是動畫每次都重寫開始播放,而逆向重複是指第一次播放完以後,第二次會倒着播放,第三次再重投開始播放動畫,第四次再到這播放動畫,如此反覆。
下面是一個具體的例子,我們通過XML定義一個屬性動畫並將其作用再View上,如下所示。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator android:propertyName="translationX"
        android:duration="3000"
        android:valueTo="100"
        android:valueType="floatType"/>

    <objectAnimator android:propertyName="translationY"
        android:duration="3000"
        android:valueTo="300"
        android:valueType="floatType"/>
</set>

如何使用上面的屬性動畫呢?也很簡單,如下所示。
        AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.property_animator);
        set.setTarget(button);
        set.start();

在實際的開發中建議採用代碼來實現屬性動畫,這時因爲通過代碼來實現比較簡單。更重要的是,很多時候一個屬性的起始值是無法提前確定的,比如讓一個Button從屏幕左邊移動到屏幕的右邊,由於我們無法提前知道屏幕的寬度,因此無法將屬性動畫定義在XML中,在這種情況下就必須通過代碼來動態的創建屬性動畫。

2.理解插值器和估值器

TimeInterpolator中文翻譯爲時間插值器,它的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比,系統預知的有LinearInterpolator(線性插值器:勻速動畫)、AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢中間快)和DecelerateInterpolator(減速插值器:動畫越來越慢)等。TypeEvaluator的中文翻譯爲類型估值算法,也叫估值器,它的作用是根據當前屬性改變的百分比來計算改變後的屬性值,系統預知的有IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點型屬性)和ArgbEvaluator(針對Color屬性)。屬性動畫中的插值器(Interpolator)和估值器(Evaluator)很重要,它們是實現非勻速動畫的重要手段。可能這麼說還有點晦澀,沒關係,下面各處一個示例就很好理解了。
如下圖所示,他是一個勻速動畫,採用了線性插值器和整型估值算法,在40ms內,View的x屬性從0到40的變換。

由於動畫的默認屬性率爲10ms/幀,所有改動畫將分爲5幀進行,我們來考慮第三幀(x=20, t=20ms),當t=20ms的時候,時間的流逝的百分比是0.5(20/40=0.5),意味着現在時間過了一般,那x應該改變多說呢?這個就是由插值器和估值算法來確定。拿線性插值器來說,當時間流逝一半的時候,x的變換應該是一般,即x的改變是0.5,爲什麼呢?因爲它是線性插值器,是實現勻速動畫的,下面看它的源碼:
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

很顯然,線性插值器的返回值和輸入值一樣,因此插值器返回的值是0.5,這意味着x改變的是0.5,這個時候插值器的工作就完成了。具體x變成了什麼值,這個需要估值算法來確定,我們來看看整型估值算法的源碼。
public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>int</code> or
     *                   <code>Integer</code>
     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

上述算法很簡答,evaluate的三個參數分別表示估值小數、開始值和結束值,對應於我們的例子就分別是0.5、0、40。根據上述算法,整型估值返回給我的結果就是20,這就是(x=20,t=20ms)的由來。
屬性動畫要求對象的該屬性由set方法和get方法(可選)。插值器和估值算法除了系統提供的外,我們還可以自定義。實現方式也很簡單,因爲插值器和估值算法都是一個接口,且內部只有一個方法,我們只要派生一個類實現接口就可以了,然後就可以做出千奇百怪的動畫效果了。具體一點就是:自定義插值器需要實現Interpolator或者TimeInterpolator,自定義估值算法需要實現TypeEvaluator。另外就是如果要對其他類型(非int、float、Color)做動畫,那麼必須要自定義類型估值算法。

3.屬性動畫的監聽器

屬性動畫提供了監聽器用於監聽動畫的播放過程,主要有如下兩個接口:AnimatorUpdateListener和AnimatorListener。

AnimatorListener的定義如下:
public static interface AnimationListener { void onAnimationStart(Animator animation); void onAnimationEnd(Animator animation); void onAnimationRepeat(Animator animation);
        void onAnimationCancel(Animator animation); }

AnimatorListen可以看出,它可以監聽動畫的開始、結束以及重複播放。同時爲了方便開發,系統還提供了AnimatorListenerAdapter這個類,他是AnimatorListener的適配器,這樣我們就可以有選擇地實現上面的4個方法了,畢竟不是所有方法都是我們感興趣的。
下面再看一下AnimatorUpdateListener的定義,如下所示。
    public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animation);
    }

AnimatorUpdateListener比較特殊,它會監聽整個動畫過程,動畫是由許多幀組成的,沒播放一幀,onAnimationUpdate就會被調用一次,利用這個特性,我們可以做一些特殊的事情。

4. 對任意屬性做動畫

這裏先提出一個問題:給Button加一個動畫,讓這個Button的寬度從當前寬度增加到500px。也許你會說,這很簡單,用View動畫就可以搞定,我們可以來試試,你能寫出來嗎? 很快你就會恍然大悟,原來View動畫根本就不支持對寬度進行動畫。沒錯,View動畫只支持四種類型:平移、旋轉、縮放、不透明度。當x方向的縮放可以讓Buttonx方向方法,看起來好像是寬度增加了,實際上不是,只是Button被放大了而已,而且由於只是x方向被放大,這個時候Button的背景以及上面的文本都被拉伸了,甚至由可能Button會超出屏幕。這樣的的效果顯然是很差的,而且也不是真正地對寬度做動畫。不過索性我們還有屬性動畫,我們用屬性動畫試試,如下所示。
    private void performAnimate() {
        ObjectAnimator.ofInt(mButton,"width",500).setDuration(5000).start();
    }

    @Override
    public void onClick(View v) {
        if (v == mButton){
            performAnimate();
        }
    }

上述代碼運行後發現沒效果,其實沒效果是對的,如果隨便傳遞一個屬性過去,輕則沒有動畫效果,重則程序直接Crash。
下面分析屬性動畫的原理:屬性動畫要求動畫作用的對象提供該屬性的set和get方法,屬性動畫根據外界傳遞的該屬性的初始值和最終值,以動畫的效果多次去調用set方法,每次傳遞給set方法的值都不一樣,確切來說是隨着時間的推移,所傳遞的值越來越接近最終值。總結一下,我們對object的屬性abc做動畫,如果想讓動畫生效,需要同時滿足兩個條件:

(1)object必須要提供setAbc方法,如果動畫的時候沒有傳遞初始值,那麼還要提供getAbc方法,因爲系統要去取abc屬性的初始值(如果這條不滿足,程序直接Crash);
(2)object的setAbc對屬性abc所做的改變必須能夠通過某種方法反映出來,比如會帶來UI的改變之類的(如果這條不滿足,動畫無效果但不會Crash)。

以上條件缺一不可。那麼爲什麼我們對Button的width屬性做動畫會沒有效果?這時因爲Button內部雖然提供了getWidth和setWidth,但是這個setWidth方法並不是改變視圖的大小,他是TextView新添加的方法,View是沒有這個setWidth方法的,由於Button繼承了TextView,所以Button也就由了setWidth方法。下面看一下這個getWidth和setWidth方法的源碼。

    @android.view.RemotableViewMethod
    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();
        invalidate();
    }
    
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

從上述源碼可以看出,getWidth的確是獲取View的寬度的,而setWidth是TextView和其子類的專屬方法,它的作用不是設置View的寬度,而是設置TextView的最大寬度和最小寬度的,這個和TextView的寬度不是一個東西。具體來說,TextView的寬度對於XML中的android:layout_width屬性,而TextView還有一個屬性android:width,這個android:width屬性就對應了TextView的setWidth。總之,TextView和Button的setWidth、getWidth乾的不是同一件事情,通過setWidth無法改變空間的寬度,所以對width做屬性動畫沒有效果。對應於動畫的兩個條件來說,本例中動畫不生效的原因是滿足了條件1而未滿足條件2.

針對上述問題,官方文檔告訴我們有三種解決方法:
  • 給你對象加上get和set方法,如果你有權限的話;

  • 用一個類來包裝原始對象,間接爲其提供get個set方法;
  • 採用ValueAnimator,監聽動畫過程,自己實現屬性的改變。

針對上面提出的三種解決辦法,下面給出具體的介紹。

1.給你的對象加上get和set方法,如果你有權限的話

這個意思很好理解,如果你有權限的話,加上get和set就搞定了。但是很多時候我們沒權限去這麼做。比如文本開頭所提到的問題,你無法給Button加上一個合乎要求的setWidth方法,因爲這時Android SDK內部實現的。這個方法最簡單,但是往往不可行的,這裏就不對其進行更多的分析了。

2.用一個類來包裝原始對象,間接爲其提供get和set方法
這是一個很有用的解決辦法,是筆者最喜歡用的,因爲用起來很方便,也很好理解,下面將通過一個具體的例子來介紹它。
    private void performAnimate() {
        ViewWrapper wrapper = new ViewWrapper(mButton);
        ObjectAnimator.ofInt(wrapper,"width",500).setDuration(5000).start();
    }

    @Override
    public void onClick(View v) {
        if (v == mButton){
            performAnimate();
        }
    }

    private static class ViewWrapper {
        private View mTarget;

        public ViewWrapper(View target) {
            this.mTarget = target;
        }
        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }
        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }

上述代碼再5s內讓Button的寬度增加到了500px,爲了達到這個效果,我們提供了ViewWrapper類專門用於包裝View,具體到本例是包裝Button。然後我們對ViewWrapper的width屬性做動畫,並且setWidth方法中修改器內部的target的寬度,而target實際上就是我們包裝的Button。這樣一個間接屬性動畫就搞定了,上述代碼同樣適用於一個對象的其他屬性。

3.採用ValueAnimator,監聽動畫過程,自己實現屬性的改變
首先說說什麼是ValueAnimatorValueAnimator本身不做用於任何對象,也就是說直接使用它沒有任何動畫效果。它可以對一個值做動畫,然後我們可以監聽其動畫過程,再動畫過程中修改我們的對象的屬性值,這樣也就相當於我們的對象做了動畫。下面用例子來說明:
    private void performAnimate(final View target, final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            //持有一個IntEvaluator對象,下面方便估值的時候使用
            private IntEvaluator mEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                //獲得當前動畫的進度值,整數,1~100之間
                int currentValue = (Integer)animator.getAnimatedValue();
                Log.d(TAG, "current value: " + currentValue);

                //獲得當前進度棧整個動畫過程的比例,浮點型,0~1之間
                float fraction = animator.getAnimatedFraction();

                //直接用用整型估值器,通過比例計算出寬度,然後再設給Button
                target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
                target.requestLayout();
            }
        });
        valueAnimator.setDuration(5000).start();
    }

    @Override
    public void onClick(View v) {
        if (v == mButton){
            performAnimate(mButton, mButton.getWidth(), 500);
        }
    }

上述代碼的效果圖和採用ViewWrapper是一樣的。關於這個ValueAnimator要再說一下,拿上面的例子來說,它會再5000ms內將一個數從1變到100,然後動畫的每一幀會回調onAnimationUpdate方法。再這個方法裏,我們可以獲取當前的值(1~100)和當前值所佔的比例,我們可以計算出Button現在的寬度應該是多少。比如時間過了一般,當前值是50,比例爲0.5,假設Button的起始寬度是100px,最終寬度是500px,那麼Button增加的寬度也應該佔總增加寬度的一半,總增加寬度是500-100=400,所以這個時候Button應該增加的寬度是400*0.5=200,那麼當前Button的寬度應該爲初始寬度+增加寬度(100+200=300)。上述計算過程很簡單,其實他就是整型估值器IntEvaluator,所以我們不用自己寫了,直接用吧。

5.屬性動畫的工作原理

屬性動畫要求動畫作用的對象提供該屬性的set方法,屬性動畫根據你傳遞的該屬性的初始值和最終值,以動畫的效果多次去調用set方法。每次傳遞set方法的值都不一樣,確切來說是隨着時間的推薦,所以傳遞的值越來越接近最終值。如果動畫的時候沒有傳遞初始值,那麼還要提供get方法,因爲系統要去獲取屬性的初始值。對於屬性動畫來說,啓動話過程中所做的就是這麼多,下面看源碼分析。

首先我們要找一個入口,就從ObjectAnimator.ofInt(mButton,"width",500).setDuration(5000).start(),其他動畫都是類似的。先看ObjectAnimator的start方法:
    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

上面代碼其實做的事情很簡單,首先會判斷如果當前動畫、等待的動畫(Pending)和延遲的動畫(Delay)中有當前動畫相同的動畫,那麼就把相同的動畫給取消掉,接下那一段是log,再接着就是調用了弗雷的super.star()方法。因爲ObjectAnimator繼承了ValueAnimator,所以接下來我們看一下ValueAnimator的Start方法:
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

可以看出屬性動畫需要運行再Lopper的線程中。上述代碼最終會調用AnimationHandler的start方法,這個AnimationHandler並不是Handler,他是一個Runnable。看一下他的代碼,通過代碼我們發現,很快就調到JNI層,不過JNI層最終還是要調回來的。它的run方法會被調用,這個Runnable涉及和底層的交互,這麼就忽略這部分,直接看重點:ValueAnimator中的doAnimationonFrame方法,如下所示。
    public final boolean doAnimationFrame(long frameTime) {
        if (mStartTime < 0) {
            // First frame. If there is start delay, start delay count down will happen *after* this
            // frame.
            mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
        }

        // Handle pause/resume
        if (mPaused) {
            mPauseTime = frameTime;
            removeAnimationCallback();
            return false;
        } else if (mResumed) {
            mResumed = false;
            if (mPauseTime > 0) {
                // Offset by the duration that the animation was paused
                mStartTime += (frameTime - mPauseTime);
            }
        }

        if (!mRunning) {
            // If not running, that means the animation is in the start delay phase of a forward
            // running animation. In the case of reversing, we want to run start delay in the end.
            if (mStartTime > frameTime && mSeekFraction == -1) {
                // This is when no seek fraction is set during start delay. If developers change the
                // seek fraction during the delay, animation will start from the seeked position
                // right away.
                return false;
            } else {
                // If mRunning is not set by now, that means non-zero start delay,
                // no seeking, not reversing. At this point, start delay has passed.
                mRunning = true;
                startAnimation();
            }
        }

        if (mLastFrameTime < 0) {
            if (mSeekFraction >= 0) {
                long seekTime = (long) (getScaledDuration() * mSeekFraction);
                mStartTime = frameTime - seekTime;
                mSeekFraction = -1;
            }
            mStartTimeCommitted = false; // allow start time to be compensated for jank
        }
        mLastFrameTime = frameTime;
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);

        if (finished) {
            endAnimation();
        }
        return finished;
    }

注意上述代碼,末尾調用了animateBaseOnTime方法,而animateBaseOnTime內部調用了animateValue,下面看animateValue的代碼:
    @CallSuper
    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

上述代碼中的calculateValue方法就是計算每幀動畫所應用的屬性的值,下面着重看一下到底是再哪裏調用屬性的get和set方法的,畢竟這個纔是我們最關心的。
在初始化的時候,如果屬性的初始值沒有提供,則get方法會被調用,情況PropertyValuesHolder的setupValue方法,可以發現get方法是通過反射來調用的,如下所示。
    private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            kf.setValue(mProperty.get(target));
        }
        try {
            if (mGetter == null) {
                Class targetClass = target.getClass();
                setupGetter(targetClass);
                if (mGetter == null) {
                    // Already logged the error - just return to avoid NPE
                    return;
                }
            }
            kf.setValue(mGetter.invoke(target));
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }


當動畫的下一幀到來的時候,PropertyValuesHolder中的setAnimatedValue方法會將新的屬性值設置給對象,調用其set方法。從下面的源碼可以看出,set方法也是通過反射來調用的:
    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章