1. 程式人生 > >Android動畫總結系列(3)——補間動畫原始碼分析

Android動畫總結系列(3)——補間動畫原始碼分析

上文總結了Android補間動畫的基本用法,Android補間動畫的原理是通過給定初始幀和結束幀的值,再通過在給定時間間隔內計算插值並重新整理介面,來形成漸變動畫效果。Android補間動畫包括四個基本效果:平移/旋轉/縮放/透明度。上文由於篇幅原因沒有解釋其原始碼,本文專門分析補間動畫的相關原始碼。 一、整體結構
Animation是四個基本動畫效果的基類,其定義了動畫的基礎屬性和獲取變換(Transformation)的基本操作模板; AnimationUtils是動畫的執行幫助類,具體見第三章描述; 平移/縮放/旋轉/透明度動畫都是Animation的子類,AnimationSet動畫集合是Animation動畫的組合; 二、基類Animation 2.1 Animation支援的XML屬性 duration:一次動畫的執行耗時; startOffset:動畫從startTime到真正執行的等待事件,也就是開始偏移時間量; fillEnabled:表示動畫開始前和結束後是否還繼續應用動畫的轉化效果,預設false; fillBefore:動畫開始執行前是否應用動畫的轉化效果,預設true;但受fillEnabled影響:fillEnabled=false,此方法值的false無效而true有效;fillEnabled=true,此方法的true/false都有效; fillAfter:動畫結束執行後是否應用動畫的轉化效果,預設false;也受fillEnabled影響:fillEnabled=false,此方法的false無效而true有效,fillEnabled=true,此方法的true/false都有效; repeatCount:動畫重複執行次數 repeatMode:動畫重複執行時的模式,可以指定重複執行是從頭執行或者逆序執行到頭 zAdjustment:動畫執行過程中待執行動畫的View的展示層次 background:動畫執行過程中的背景 detachWallpaper:如果動畫是Window動畫,而Window設定了背景,則此值true表示Window展示動畫,而背景保持不動,不執行動畫;false表示背景和Window一起執行動畫; interpolator:動畫的插值器 2.2 構造器
2.2.1 Animation() 構造一個空動畫(執行時間0,預設插值器,fillBefore為true而fillAfter為false) 2.2.2 Animation (Context context, AttributeSet attrs) 通過資源初始化一個動畫,初始化上面列出的xml屬性 2.3 關鍵方法與內部類 與set方法相對的get方法略去。 2.3.1 void reset():重置動畫狀態,回到初始初始狀態; 2.3.2 void cancel():取消動畫執行,此操作會呼叫動畫監聽器告知動畫結束,如果手動呼叫cancel,則想要再次執行動畫需要呼叫reset; 2.3.3
 boolean isInitialized():判斷動畫是否已經初始化(initialized); 2.3.4 void initialize( int width, int height, int parentWidth, int parentHeight):傳入需待執行動畫的物件(一般是View)和其父元素的寬高,來初始化動畫,這是為了支援上文中提到的RELATIVE_TO_SELF和RELATIVE_TO_PARENT屬性;當待執行動畫的物件和其父元素的寬高確定時,就應該呼叫此方法,同時,此方法應在getTransformation前呼叫。width/height代表待執行動畫物件的寬/高,parentWidth/parentHeight代表待執行動畫物件父元素的寬/高;
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    reset();
    mInitialized = true;
}
需要注意的是,這段程式碼並沒有真的對寬高進行操作,而是在子類中覆寫此方法來使用寬高的。 2.3.5 void setListenerHandler(Handler handler):設定回撥動畫事件的Handler,注意,這個方法不是對外可見的,我們一般也不用上,除非說強烈需要動畫回撥到某個程序內去; 2.3.6 void setInterpolator(Context context, @InterpolatorRes int resID):設定動畫插值器,傳入resId; 2.3.7 void setInterpolator(Interpolator i):設定動畫插值器,常用; 2.3.8 void setStartOffset( long startOffset):設定動畫開始執行的時間偏移量(動畫開始執行相對其開始執行時間的偏差),單位為毫秒,動畫從時間startTime + startOffset開始執行,在AnimationSet中,某些子動畫需要延遲執行,則可呼叫此方法; 2.3.9 void setDuration( long durationMillis):設定動畫執行時間,不能為負數; 2.3.10 void restrictDuration( long durationMillis):指定動畫最長可以執行的時間;此方法實現在預期執行動畫時間超出durationMillis時,通過減少duration和repeatCount的值來保證動畫總執行時間不超過durationMillis;動畫的一次執行時間等於 startOffset + duration; 2.3.11 void scaleCurrentDuration( float scale):將duration和startOffset縮放scale倍; 2.3.12 void setStartTime( long startTimeMillis):設定動畫開始執行的時間;如果值被設為START_ON_FIRST_FRAME,動畫將在第一次呼叫getTransformation時開始執行;此方法傳入的startTimeMillis應使用;AnimationUtils.currentAnimationTimeMillis而非System.currentTimeMillis()獲取; 2.3.13 void start():第一次呼叫getTransformation時開始執行動畫; 2.3.14 void startNow():在當前時間開始執行動畫; 2.3.15 void setRepeatMode( int repeatMode):設定動畫重複模式,標識動畫執行到結束幀時該如何執行;RESTART表示從初始幀執行動畫,REVERSE表示從結束幀到初始幀執行動畫;僅在repeatCount>0或者動畫無限執行時有效果; 2.3.16 void setRepeatCount( int repeatCount):設定動畫重複執行次數; 2.3.17 boolean isFillEnabled():fillEnabled為true,表示動畫會應用fillBefore的設定; 2.3.18 void setFillEnabled( boolean fillEnabled):設定fillEnabled值,表示是否需要考慮fillBefore屬性,true表示會應用fillBefore屬性,false表示fillBefore屬性會被忽略,動畫轉化效果只能在動畫結束時才會被應用到物件展示上; 2.3.19 void setFillBefore( boolean fillBefore):fillBefore為true表示在動畫開始執行前,就將其屬性應用到View展示上;注意一點動畫集合也適用此邏輯,在動畫集合AnimationSet未開始執行時,動畫效果也不會應用在介面展示上; 2.3.20 void setFillAfter( boolean fillAfter):true表示在動畫執行結束後,動畫效果還持續存在,false表示執行結束後View展示回到原始位置,也就是View的可視區域的大小; 2.3.21 void setZAdjustment( int zAdjustment):設定動畫執行時View的Z軸層次展示模式。取值有三種: Animation.ZORDER_NORMAL:待執行動畫View保持在當前Z軸層次上; Animation. ZORDER_TOP:待執行動畫View在執行動畫過程中的層次強制在所有其他View的上方; Animation.ZORDER_BOTTOM:與ZORDER_TOP相反,待執行動畫View在執行動畫過程中層次強制在所有其他View的下方; 2.3.22 void setBackgroundColor(@ColorInt int bg):設定動畫的背景 2.3.23 float getScaleFactor():縮放因子在動畫執行過程中影響與View座標相關的值如中心點等;在getTransformation(long, Transformation, float)方法中設定該值,其實就是第三個引數;一般在applyTransformation呼叫此方法獲取縮放值; 2.3.24 void setDetachWallpaper( boolean detachWallpaper):此值為true且動畫應用於有桌布的Window,則Window將與桌布脫離執行動畫,也就是說,在動畫執行過程中,桌布保持不動。false則桌布跟隨window的動畫變動; 2.3.25 boolean willChangeTransformationMatrix():標識動畫是否會影響transformation matrix(轉化矩陣),透明度動畫不影響此矩陣,而縮放動畫會影響此矩陣; 2.3.26 boolean willChangeBounds():標識動畫是否會影響View的邊界,透明度動畫不會影響,而 >100%的縮放動畫會影響邊界;注意,這裡影響了邊界也並不會導致View重新佈局(layout);不管View縮放到多大,都只會影響View的可視寬高,不影響其佈局寬高,具體見上一篇文章。 2.3.27 void setAnimationListener(AnimationListener listener):設定動畫事件監聽,能監聽的事件有三個:動畫開始、動畫結束和動畫重複 2.3.28 long computeDurationHint():估算動畫應該會執行多長時間,正常情況下只要重寫此類的動畫沒特意調整,此值都是精確的; 2.3.29 boolean getTransformation( long currentTime, Transformation outTransformation, float scale):獲取在指定時間點的transformation,此方法會修改outTransformation的值,並返回動畫是否還在執行的標誌 currentTime:當前的動畫執行位置,值是真實世界時間(wall clock time); outTransformation:呼叫方傳入的transformation,由動畫填充後供呼叫方使用; scale:縮放因子,應用於所有的轉化動作(transform operation); 返回值:true表示動畫還在執行,false表示動畫不再執行; 2.3.30 boolean hasStarted():動畫是否已經開始執行; 2.3.31 boolean hasEnded():動畫是否已經結束執行; 2.3.32 void applyTransformation( float interpolatedTime, Transformation t):子類實現此方法根據插值時間來應用其轉化效果,比如說透明度動畫,在時間0.5時的透明度等;interpolatedTime是0~1之間的規整化時間; 2.3.33 通過type解析值,這就是上文中說的三個引數的值解析方法
protected float resolveSize(int type, float value, int size, int parentSize) {
    switch (type) {
        case ABSOLUTE :
            return value;
        case RELATIVE_TO_SELF :
            return size * value;
        case RELATIVE_TO_PARENT :
            return parentSize * value;
        default:
            return value;
    }
}

2.3.34 Description內部類:完成xml佈局中動畫尺寸的解析工作,xml佈局中絕對值以dp結尾,相對於自己以%結尾,相對於父元素以%p結尾。 2.4 核心方法 我們在2.3中基本列出了所有Animation類的屬性設定與其意義。但Animation最核心的一個方法boolean getTransformation( long currentTime, Transformation outTransformation)我們並未討論。此處專開一節討論此方法。 我們直接在原始碼內講解此方法:
/**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
* 此方法的作用是計算指定時間應該對View應用的transformation效果。此方法的實現會改變外部傳入的outTranformation的內容從而形成動畫效果。
*
* @param currentTime Where we are in the animation. This is wall clock time.當前我們在動畫的位置,此時間是真實時間,也就是System.currentTimeMillis時間
* @param outTransformation A transformation object that is provided by the
*        caller and will be filled in by the animation.外部傳入的Transformation物件,動畫會根據當前動畫執行時間時間來調整tranformation物件的內容
* @return True if the animation is still running;返回true表示動畫還在執行,false表示動畫執行結束了
*/
public boolean getTransformation(long currentTime, Transformation outTransformation) {
    if (mStartTime == -1) {
        //前文描述了一種設定startTime為START_ON_FIRST_FRAME動作,表示動畫在介面繪製的第一幀開始執行,這裡就是其StartTime的真正賦值
        //後文描述了當動畫需要多次執行時,每次執行結束後startTime會置為-1,重新經此方法決定動畫startTime
        mStartTime = currentTime;
    }

    //獲取動畫的執行偏移時間,動畫真正的執行耗時是startTime + startOffset。startOffset表示動畫在startTime後還要等待startOffset才執行
    final long startOffset = getStartOffset();
    //獲取動畫執行一次的耗時
    final long duration = mDuration;
    float normalizedTime;
    if (duration != 0) {
        //計算當前時間在動畫一次執行過程中的位置,此值如果是在startOffset範圍內,也就是動畫還沒執行,則結果為<0;
        //開始執行時為0,如果是執行過程中肯定<1,執行結束後則一定>=1
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        // time is a step-change with a zero duration
        //duration為0,則是初始幀和結束幀來回切換的動畫,時間只有0 1兩種取值
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f ;
    }

    //動畫一次執行結束了
    final boolean expired = normalizedTime >= 1.0f;
    //mMore表示動畫是否還有幀可以執行,預設是隻執行一次,所以一次後mMore=false;後面會根據repeatCount重新決定此值
    mMore = !expired;

    //mFillEnabled為false,直接將時間規整到0~1之間(FillEnabled預設值是false)
    if (!mFillEnabled) normalizedTime = Math.max(Math. min(normalizedTime, 1.0f ), 0.0f );

    //fillBefore支援時間<0,fillAfter支援時間>1
    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        if (!mStarted) {
            //通知動畫開始
            fireAnimationStart();
            mStarted = true;
            if (USE_CLOSEGUARD) {
                guard.open( "cancel or detach or getTransformation");
            }
        }

        //mFillBefore或mFillEnd導致規整時間不在0~1之間,再規整一遍
        if (mFillEnabled) normalizedTime = Math.max(Math. min(normalizedTime, 1.0f ), 0.0f );

        //動畫的重複模式reverse時,往回執行
        if (mCycleFlip) {
            normalizedTime = 1.0f - normalizedTime;
        }

        //獲取插值時間,因為均勻時間被插值器打亂成插值時間,形成了動畫的速率變化效果
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
        //子類實現此方法,來真正的應用動畫效果
        applyTransformation(interpolatedTime, outTransformation);
    }

    if (expired) {
        if (mRepeatCount == mRepeated) {
            //動畫執行次數已經全部執行完成,則通知動畫結束
            if (!mEnded) {
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
            if (mRepeatCount > 0) {
                //已執行次數+1
                mRepeated++;
            }

            if (mRepeatMode == REVERSE) {
                //動畫執行結束了一輪後,動畫執行是否需要反轉的標誌位要切換過來
                mCycleFlip = ! mCycleFlip;
            }

            //startTime設為-1,下次呼叫此方法時重新賦值
            mStartTime = -1;
            //因為動畫輪數沒有執行完成,所以mMore置為true
            mMore = true;

            //告知動畫重複執行事件
            fireAnimationRepeat();
        }
    }

    //動畫已經執行結束了,但是OneMoreTime為true,返回標識告訴外面動畫仍在執行
    //mOneMoreTime預設值為true,僅此處和cancel介面會置此值為false,所以,外部還會再調一次getTransfrom,這時候就是介面會考慮fillAfter來決定
    if (!mMore && mOneMoreTime) {
        mOneMoreTime = false;
        return true;
    }

    //返回動畫是否還在執行
    return mMore;
}
此處實現的是通用的動畫改變transformation前後的通用處理,由子類實現applyTransformation來真正的填充轉化效果。 三、AnimationUtils類 AnimationUtils內所有方法都是靜態的,因為此類是幫助類,不需要初始化,此節講解一些此類的介面。 3.1相關介面 3.1.1 long currentAnimationTimeMillis():返回的是SystemClock. uptimeMillis(),此值不等同於System.currentTimeMillis; 3.1.2 Animation loadAnimation(Context context, @AnimRes int id):從佈局檔案中解析出動畫,此方法包含的是從xml到動畫的XML解析過程; 3.1.3 支援解析set/translate/alpha/rotate/scale五種標籤; 3.1.4 LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id):解析LayoutAnimation,本文不涉及此知識,LayoutAnimation支援layoutAnimation、gridLayoutAnimation標籤; 3.1.5 Animation makeInAnimation(Context c, boolean fromLeft):返回一個左右平移和透明度效果的View顯示動畫; 3.1.6 Animation makeOutAnimation(Context c, boolean toRight):返回一個左右平移和透明度效果的View消失動畫; 3.1.7 Animation makeInChildBottomAnimation(Context c):返回一個子元素顯示動畫,動畫支援從下到上平移和透明度效果; 3.1.8 Interpolator loadInterpolator(Context context, @InterpolatorRes int id):載入一個插值器;支援的標籤有: linearInterpolator:線性插值,勻速 accelerateInterpolator:加速插值 decelerateInterpolator:減速插值 accelerateDecelerateInterpolator:先加速再減速插值 cycleInterpolator:迴圈插值,沒研究過 anticipateInterpolator:沒研究 overshootInterpolator:沒研究 anticipateOvershootInterpolator:沒研究 bounceInterpolator:沒研究 pathInterpolator:沒研究 四、基本效果相關類 4.1 TranslateAnimation 4.1.1 支援的XML屬性 fromXDelta/toXDelta/fromYDelta/toYDelta:具體見上一篇文章(補間動畫使用),由上文中的Description類解析。絕對偏移量用dp/px標識,相對於自己用%標識,相對於父元素用%p標識。程式碼見TranslateAnimation(Context context, AttributeSet attrs); 4.1.2 其他構造方法: TranslateAnimation( float fromXDelta, float toXDelta, float fromYDelta, float toYDelta):呼叫此構造方法傳入的都認為是絕對偏移值 TranslateAnimation( int fromXType, float fromXValue, int toXType, float toXValue,   int fromYType, float fromYValue, int toYType, float toYValue):呼叫此構造方法可以傳入相對值 4.1.3 初始化動畫寬高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);
    mFromXDelta = resolveSize( mFromXType, mFromXValue , width, parentWidth);
    mToXDelta = resolveSize( mToXType, mToXValue , width, parentWidth);
    mFromYDelta = resolveSize( mFromYType, mFromYValue , height, parentHeight);
    mToYDelta = resolveSize( mToYType, mToYValue , height, parentHeight);
}
上文中提到的mFromXValue/mToXValue/mFromYValue/mToYValue可能是絕對值,也可能是相對View自己或相對View父元素的百分比,此處就是要根據各值的Type和當前元素寬高及父元素寬高,來算出最終的絕對偏移量。計算結果存在mFromXDelta/mToXDelta/mFromYDelta/mToYDelta四個成員變數內。 4.1.4 填充轉化過程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    float dx = mFromXDelta;
    float dy = mFromYDelta;
    //簡單的插值計算,計算出X軸在規整化的時間interpolatedTime時的偏移量
    if ( mFromXDelta != mToXDelta) {
        dx = mFromXDelta + (( mToXDelta - mFromXDelta ) * interpolatedTime);
    }

    //計算出y軸在規整化的時間interpolatedTime時的偏移量
    if ( mFromYDelta != mToYDelta) {
        dy = mFromYDelta + (( mToYDelta - mFromYDelta ) * interpolatedTime);
    }
    //對轉化矩陣設定平移效果
    t.getMatrix().setTranslate(dx, dy);
}
核心的程式碼就這麼多,一個平移動畫就出來了~~~ 4.2 ScaleAnimation 4.2.1 支援的XML屬性 fromXScale/toXScale/fromYScale/toYScale/pivotX/pivotY,具體用法見前一篇文章(補間動畫使用),pivotX和pivotY是由Description解析的,其他的四個縮放比例是ScaleAnimation解析的。 4.2.2 其他構造方法: ScaleAnimation( float fromX, float toX, float fromY, float toY):傳入各縮放比例 ScaleAnimation( float fromX, float toX, float fromY, float toY, float pivotX, float pivotY):傳入各縮放比例和絕對的中心點 ScaleAnimation( float fromX, float toX, float fromY, float toY,   int pivotXType, float pivotXValue, int pivotYType, float pivotYValue):傳入各縮放比例,和各種型別的中心點定義 4.2.3 初始化動畫寬高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);

    mFromX = resolveScale( mFromX, mFromXType , mFromXData , width, parentWidth);
    mToX = resolveScale( mToX, mToXType , mToXData , width, parentWidth);
    mFromY = resolveScale( mFromY, mFromYType , mFromYData , height, parentHeight);
    mToY = resolveScale( mToY, mToYType , mToYData , height, parentHeight);

    mPivotX = resolveSize( mPivotXType, mPivotXValue , width, parentWidth);
    mPivotY = resolveSize( mPivotYType, mPivotYValue , height, parentHeight);
}
//從value中解析出絕對寬高,然後與View的寬高相比,返回縮放比例
float resolveScale( float scale, int type, int data, int size, int psize) {
    float targetSize;
    //這兩種type的值是絕對寬高值,解析出來,放到後面與View寬高做比率
    if (type == TypedValue. TYPE_FRACTION) { 
        targetSize = TypedValue.complexToFraction(data, size, psize);
    } else if (type == TypedValue.TYPE_DIMENSION ) {
        targetSize = TypedValue.complexToDimension(data, mResources .getDisplayMetrics());
    } else {
        //scale本身就是比例
        return scale;
    }

    if (size == 0) {
        return 1 ;
    }

    return targetSize/( float)size;
}
4.2.4 填充轉化過程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    float sx = 1.0f ;
    float sy = 1.0f ;
    float scale = getScaleFactor();

    //計算出當前X軸的縮放比率
    if ( mFromX != 1.0f || mToX != 1.0f) {
        sx = mFromX + (( mToX - mFromX ) * interpolatedTime);
    }
    //計算出當前Y軸的縮放比例
    if ( mFromY != 1.0f || mToY != 1.0f) { 
        sy = mFromY + (( mToY - mFromY ) * interpolatedTime);
    }

    if ( mPivotX == 0 && mPivotY == 0 ) {
        //如果中心點就是View左上角(0,0)
        t.getMatrix().setScale(sx, sy);
    } else {
        //如果設定了指定的中心點(中心點受外部的縮放因子影響)
        t.getMatrix().setScale(sx, sy, scale * mPivotX , scale * mPivotY);
    }
}
4.3 RotateAnimation 4.3.1 支援的XML屬性 fromDegrees/toDegrees:動畫開始的旋轉角度和動畫結束的旋轉角度,值是360度制的。 4.3.2 其他構造方法: RotateAnimation( float fromDegrees, float toDegrees):傳入動畫開始結束的旋轉角度,旋轉中心點是View左上角; RotateAnimation( float fromDegrees, float toDegrees, float pivotX, float pivotY):傳入動畫開始結束的旋轉角度和絕對的中心點座標,動畫中心點(X+pivotX, Y+pivotY); RotateAnimation (float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,   int pivotYType, float pivotYValue):傳入旋轉角度和各種型別的中心點座標 4.3.3 初始化動畫寬高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);
    //使用Animation中的計算方法計算中心點,傳入的當前元素寬高和父元素寬高
    mPivotX = resolveSize( mPivotXType, mPivotXValue , width, parentWidth);
    mPivotY = resolveSize( mPivotYType, mPivotYValue , height, parentHeight);
}
4.3.4 填充轉化過程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    //計算插值的旋轉角度
    float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
    float scale = getScaleFactor();
   
    if ( mPivotX == 0.0f && mPivotY == 0.0f ) {
        //繞(0,0)旋轉
        t.getMatrix().setRotate(degrees);
    } else {
        //繞中心點旋轉
        t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
    }
}

4.4 AlphaAnimation 4.4.1 支援的XML屬性 fromAlpha/toAlpha設定動畫開始時的透明度和結束時透明度,值在0~1之間 4.4.2 其他構造方法: AlphaAnimation( float fromAlpha, float toAlpha):傳入動畫開始結束的Alpha值 4.4.3 初始化動畫寬高:不需要 4.4.4 填充轉化過程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    final float alpha = mFromAlpha;
    //對變換設定透明度
    t.setAlpha(alpha + (( mToAlpha - alpha) * interpolatedTime));
}

4.4.5 其他方法
public boolean willChangeTransformationMatrix() {
    //告知外層呼叫方(也就是View),此動畫不改變變換矩陣,
    //上面程式碼也看到了,Alpha改變的是Transformation本身,並不影響Matrix,這與其他三種變換不同
    return false;
}

public boolean willChangeBounds() {
    //告知呼叫方此動畫不改變View邊界
    return false ;
}

public boolean hasAlpha() {
    //告知呼叫方此動畫需要透明度支援
    return true ;
}

4.5 AnimationSet 動畫集合使用列表來儲存子動畫: private ArrayList<Animation> mAnimations = new ArrayList<Animation>(); //儲存的是所有的子動畫 private long [] mStoredOffsets ; //存放的是每個子動畫自己定義的StartOffset屬性 4.5.1 支援的XML屬性 shareInterpolator:true表示所有子元素都使用AnimationSet的插值,如果xml不設,則程式碼中預設使用加減速插值器 Android 4.0之後增加了duration/fillBefore/fillAfter/repeatMode/startOffset屬性是否設定的儲存; 注意:以上所有屬性在AnimationSet內都存Boolean值(其實是mFlags記憶體放多個Int MASK),真實值在Animation類中儲存。 4.5.2 其他構造方法: AnimationSet( boolean shareInterpolator):傳入是否共享插值器 4.5.3 初始化動畫寬高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);

    //是否設定了動畫總時長
    boolean durationSet = ( mFlags & PROPERTY_DURATION_MASK ) == PROPERTY_DURATION_MASK ;
    //是否設定了fillAfter屬性,後面幾條類似
    boolean fillAfterSet = ( mFlags & PROPERTY_FILL_AFTER_MASK ) == PROPERTY_FILL_AFTER_MASK ;
    boolean fillBeforeSet = ( mFlags & PROPERTY_FILL_BEFORE_MASK ) == PROPERTY_FILL_BEFORE_MASK ;
    boolean repeatModeSet = ( mFlags & PROPERTY_REPEAT_MODE_MASK ) == PROPERTY_REPEAT_MODE_MASK ;
    boolean shareInterpolator = ( mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK )
            == PROPERTY_SHARE_INTERPOLATOR_MASK; 
    boolean startOffsetSet = ( mFlags & PROPERTY_START_OFFSET_MASK )
            == PROPERTY_START_OFFSET_MASK;

    //如果shareInterpolator設定了,而外部沒有插值器,則使用加速減速插值器
    if (shareInterpolator) {
        ensureInterpolator();
    }

    final ArrayList<Animation> children = mAnimations ;
    final int count = children.size();

    final long duration = mDuration;
    final boolean fillAfter = mFillAfter;
    final boolean fillBefore = mFillBefore;
    final int repeatMode = mRepeatMode;
    final Interpolator interpolator = mInterpolator;
    final long startOffset = mStartOffset;


    long [] storedOffsets = mStoredOffsets;
    if (startOffsetSet) {
        //設定了startOffset,則新建一個子動畫個數的storedOffsets,儲存各動畫的開始執行延遲
        if (storedOffsets == null || storedOffsets.length != count) {
            storedOffsets = mStoredOffsets = new long[count];
        }
    } else if (storedOffsets != null) {
        //如果沒設定startOffset,則此處不用儲存子動畫的延遲
        storedOffsets = mStoredOffsets = null;
    }

    for ( int i = 0; i < count; i++) {
        Animation a = children.get(i);
        if (durationSet) {
            //所有子元素都使用AnimationSet的執行時間
            a.setDuration(duration);
        }
        if (fillAfterSet) {
            //所有子元素都使用AnimationSet的fillAfter屬性
            a.setFillAfter(fillAfter);
        }
        if (fillBeforeSet) {
            //所有子元素都使用AnimationSet的fillBefore屬性
            a.setFillBefore(fillBefore);
        }
        if (repeatModeSet) {
            ////所有子元素都使用AnimationSet的repeatMode屬性
            a.setRepeatMode(repeatMode);
        }
        if (shareInterpolator) {
            //所有子元素都使用AnimationSet的插值器
            a.setInterpolator(interpolator);
        }
        if (startOffsetSet) {
            //所有子元素的延遲時間都加上集合的延遲時間,而子元素的延遲時間存在陣列內,留待後面reset時回覆子元素的startOffset屬性
            long offset = a.getStartOffset();
            a.setStartOffset(offset + startOffset);
            storedOffsets[i] = offset;
        }
        //子動畫執行初始化
        a.initialize(width, height, parentWidth, parentHeight);
    }
}

4.5.4 填充轉化過程(applyTransformation)
public boolean getTransformation( long currentTime, Transformation t) {
    final int count = mAnimations.size();
    final ArrayList<Animation> animations = mAnimations ;
    final Transformation temp = mTempTransformation ;

    //動畫是否還需要執行
    boolean more = false;
    //動畫是否已開始
    boolean started = false;
    //動畫是否已執行完成
    boolean ended = true;

    t.clear();

    for ( int i = count - 1; i >= 0; --i) {
        final Animation a = animations.get(i);

        temp.clear();
        //應用每個子動畫的效果
        more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
        //組成最終的轉換
        t.compose(temp);

        //某個子動畫start後此值就更新為true
        started = started || a.hasStarted();
        //所有子動畫都end後此值才為true
        ended = a.hasEnded() && ended;
    }

    //對外通知動畫開始
    if (started && !mStarted) {
        if (mListener != null) {
            mListener.onAnimationStart( this);
        }
        mStarted = true;
    }

    //對外通知動畫結束,此方法不通知動畫重複,因為動畫集合不支援重複,只有子動畫通知
    if (ended != mEnded) {
        if (mListener != null) {
            mListener.onAnimationEnd( this);
        }
        mEnded = ended;
    }

    //返回動畫是否還在執行
    return more;
}

4.5.5 其他介面
//克隆時需要將動畫列表深拷貝
protected AnimationSet clone() throws CloneNotSupportedException {
    final AnimationSet animation = (AnimationSet) super .clone();
    animation.mTempTransformation = new Transformation();
    animation.mAnimations = new ArrayList<Animation>();

    final int count = mAnimations.size();
    final ArrayList<Animation> animations = mAnimations ;

    for ( int i = 0; i < count; i++) {
        animation.mAnimations.add(animations.get(i).clone());
    }

    return animation;
}
public boolean hasAlpha() {
    if ( mDirty) {
        //mDirty本身意義是標識集合中加入了一個Animation,由於加入的Animation可能hasAlpha,所以用mDirty標識我們需要重新遍歷列表
        mDirty = mHasAlpha = false ;

        final int count = mAnimations.size();
        final ArrayList<Animation> animations = mAnimations ;
        //遍歷子元素來判斷是否有Alpha變化
        for ( int i = 0; i < count; i++) { 
            if (animations.get(i).hasAlpha()) {
                mHasAlpha = true ;
                break;
            }
        }
    }

       //如果mDirty為false,則標識列表內動畫集合沒有發生變化,所以直接返回已經計算好的mHasAlpha,減少效能效能消耗
    return mHasAlpha ;
}

//通常而言,此方法發生在呼叫start之前
public void addAnimation(Animation a) {
    mAnimations .add(a);

    //新增的動畫可能會引起Matrix變化,此處重新計算
    boolean noMatrix = ( mFlags & PROPERTY_MORPH_MATRIX_MASK ) == 0 ;
    if (noMatrix && a.willChangeTransformationMatrix()) {
        mFlags |= PROPERTY_MORPH_MATRIX_MASK;
    }
    //新增的動畫可能會改變邊界,此處重新計算;changeBounds為true表示現有的動畫不引起邊界變化
    boolean changeBounds = ( mFlags & PROPERTY_CHANGE_BOUNDS_MASK ) == 0 ;
    if (changeBounds && a.willChangeBounds()) {
        mFlags |= PROPERTY_CHANGE_BOUNDS_MASK; 
    }

    if (( mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK ) {
        //動畫設定過總時長,如果子元素也設定了startOffset,此值應當是mStartOffset+mDuration與子元素startOffset的和,但此處忽略了子元素的startOffset
        mLastEnd = mStartOffset + mDuration;
    } else {
        //動畫沒設定總時長
        if ( mAnimations.size() == 1) {
            //計算動畫時長和最終結束時間
            mDuration = a.getStartOffset() + a.getDuration();
            mLastEnd = mStartOffset + mDuration;
        } else {
            //動畫最終結束時間是現有最終結束時間與新增動畫的執行時間中取較大值
            //這裡我認為mLastEnd應當與mStartOffset + a.getStartOffset() + a.getDuration()做比較
            //如果此方法發生在初始化寬高之後,a.getStartOffset就是包含mStartOffset的,如果發生在其之前,此處mLastEnd計算可能不精確
            mLastEnd = Math. max( mLastEnd, a.getStartOffset() + a.getDuration());
            mDuration = mLastEnd - mStartOffset;
        }
    }

    //下次計算hasAlpha時表示新增了動畫,重新計算
    mDirty = true;
}
五、總結 本文分析了補間動畫的原始碼,總結了補間動畫的實現思路,總體來講,補間動畫就是外部呼叫方(View)不斷的傳入真實執行時間,動畫根據真實時間計算插值時間,再根據插值時間計算當前位置的轉化效果,並應用在外部傳入的Transformation物件上,而外部呼叫方使用此Transformation對View展示進行轉化,最終形成插值動畫效果。