View Animation 執行原理解析

huansky發表於2020-07-18

Android 平臺目前提供了兩大類動畫,在 Android 3.0 之前,一大類是 View Animation,包括 Tween animation(補間動畫),Frame animation(幀動畫),在 Android 3.0 中又引入了一個新的動畫系統:Property Animation,即屬性動畫。本篇文章主要介紹 View Animation 的執行原理。

View Animation 可以使檢視執行補間動畫。即給定兩個關鍵幀,然後中間部分按照一定的演算法自動計算補充。

補間動畫的繼承關係:

 

下面看一個示例,比如想要對透明度做一個變化:

Animation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setDuration(1000);
imageView.startAnimation(alphaAnimation);

 

 那麼這個動畫是如何實現的呢,下面就要講述其實現原理。 

為了探究補間動畫的實現原理,需要對相關原始碼進行解讀,原始碼版本為 Android API 29 Platform。在解讀之前,大家可以試著回答下面這些問題。

  1. 為什麼移動位置後,點選事件的響應依舊是在原來位置上?

  2. 如果想知道動畫的執行進度,是如何獲取呢?

  3. 如果對 View 做放大縮小得動畫,那麼其寬度高度值是否會變化。

相關類介紹

下面開始原始碼分析。首先看下基類 Animation。

public abstract class Animation implements Cloneable {

}

Animation 是一個抽象類,裡面包含了各種動畫相關的屬性(時長,起始時間,重複次數,插值器等),回撥(listeners)。該類整體比較簡單,大家直接看原始碼就好。

alphaAnimation

下面來看下 Animation 子類,為了方便,本次就只說說 AlphaAnimation。

public class AlphaAnimation extends Animation {
    private float mFromAlpha;
    private float mToAlpha;

    /**
     * Constructor used when an AlphaAnimation is loaded from a resource. 
     * 
     * @param context Application context to use
     * @param attrs Attribute set from which to read values
     */
    public AlphaAnimation(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        TypedArray a =
            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation);
        
        mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);
        mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);
        
        a.recycle();
    }
    
    /**
     * Constructor to use when building an AlphaAnimation from code
     * 
     * @param fromAlpha Starting alpha value for the animation, where 1.0 means
     *        fully opaque and 0.0 means fully transparent.
     * @param toAlpha Ending alpha value for the animation.
     */
    public AlphaAnimation(float fromAlpha, float toAlpha) {
        mFromAlpha = fromAlpha;
        mToAlpha = toAlpha;
    }
    
    /**
     * Changes the alpha property of the supplied {@link Transformation} 
   * 實現動畫的關鍵函式,這裡通過當前的播放進度,計算當前的透明度,然後將其賦值給 Transformation 例項
*/ @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float alpha = mFromAlpha; t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime)); } @Override public boolean willChangeTransformationMatrix() { return false; } @Override // 不改變邊界 public boolean willChangeBounds() { return false; } /** * @hide */ @Override public boolean hasAlpha() { return true; } }

整個程式碼也是很簡單,其實很多邏輯都在基類處理了。然後子類只需要重寫一些和自己動畫相關的方法就好。其中 applyTransformation 是實現某一種動畫的關鍵,每個子類都必須重寫。

這裡需要注意的是,Transformation 就是用來儲存每一次動畫的引數。其中平移,旋轉,縮放都是通過改變 Matrix 實現的,而透明度則是改變 Alpha 值實現的。

為了便於大家進一步理解,可以在看看 AnimationSet。

AnimationSet

因為 AnimationSet 是其他幾個子類得集合體,所以看看它的程式碼邏輯還是可以發現一些不一樣的。其內部程式碼比較多,就不貼出來了。只是挑一部分講下:

    /**
     * Add a child animation to this animation set.
     * The transforms of the child animations are applied in the order
     * that they were added
     * @param a Animation to add.
     */
    public void addAnimation(Animation a) {
     // 陣列來儲存動畫 mAnimations.add(a);
boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0; if (noMatrix && a.willChangeTransformationMatrix()) { mFlags |= PROPERTY_MORPH_MATRIX_MASK; } 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) { mLastEnd = mStartOffset + mDuration; } else { if (mAnimations.size() == 1) { mDuration = a.getStartOffset() + a.getDuration(); mLastEnd = mStartOffset + mDuration; } else { mLastEnd = Math.max(mLastEnd, mStartOffset + a.getStartOffset() + a.getDuration()); mDuration = mLastEnd - mStartOffset; } } mDirty = true; } /** * The transformation of an animation set is the concatenation of all of its * component animations. * * @see android.view.animation.Animation#getTransformation
   * true 表示動畫還在執行
*/ @Override 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();
       // 通過 temp 來獲取每個 Animation 的 transformation more
= a.getTransformation(currentTime, temp, getScaleFactor()) || more;
       // 將各種動畫引數組合在一起,注意 t 是引用物件,所以這裡改了之後,外面拿到的也是改了的。 t.compose(temp); started
= started || a.hasStarted(); ended = a.hasEnded() && ended; }      // 是否開始了 if (started && !mStarted) { dispatchAnimationStart(); mStarted = true; } if (ended != mEnded) { dispatchAnimationEnd(); mEnded = ended; } return more; }

上面是 AnimationSet 中我認為兩個比較重要的方法:

  • addAnimation:將其他動畫型別新增到 set 裡面,內部實際上是通過一個 list 來儲存的。然後將每個動畫的各種屬性都記錄下。

  • getTransformation:獲取每個動畫的下一個動畫引數,然後將其組合在一起。

前面介紹了 Animation 一些背景知識。到這裡,大家多少會有一些認識了。接下去就按照呼叫流程來分析動畫的執行。

原始碼解析

View.startAnimation()

    public void startAnimation(Animation animation) {
     // 傳入的值是-1,代表準備動畫了 animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidateParentCaches(); // 給 parent 的 mPrivateFlag 加了一個 PFLAG_INVALIDATED invalidate(
true); // 其目的就是將其和子 view 的 drawing 快取都標記為無效,然後可以 redrawn } public void setAnimation(Animation animation) {
     // View 中有個屬性是用來儲存當前的 Animation 的 mCurrentAnimation
= animation; if (animation != null) { // If the screen is off assume the animation start time is now instead of // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time // would cause the animation to start when the screen turns back on if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) { animation.setStartTime(AnimationUtils.currentAnimationTimeMillis()); } animation.reset(); } }

 簡而言之 startAnimation 主要是做這麼幾件事情:

  1. 開始動畫前,先告知 Animation,可以做一些準備,包括部分引數的賦值;

  2. 更新 View 自身的 Animation 的屬性值;

  3. 給父 View 的 mPrivateFlag 加上 PFLAG_INVALIDATED 屬性;

  4. 將自身和子 view 的 draw cache 都標記為無效的,通過 父 View 呼叫 invalidateChild 促發 redrawn;

ViewGroup.invalidateChild

下面看下 invalidateChild 是怎麼促發重繪的:

// ViewGroup    
 public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // HW accelerated fast path
            onDescendantInvalidated(child, child);
            return;
        }

        ViewParent parent = this;
        if (attachInfo != null) {
           //  ..... 省略非關鍵性程式碼 

            do { // 無限迴圈
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                if (view != null) {
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
                    }
                }
          // 最終呼叫的是該方法來重繪
                parent = parent.invalidateChildInParent(location, dirty);
                //  .... 省略非關鍵性程式碼 
            } while (parent != null);  // 終止條件是找不到父 View 了。
        }
    }

這裡很主要的一件事,找到 rooView ,然後呼叫了 invalidateChildInParent 方法。這裡的 rootView 其實就是 ViewRootImpl,至於為啥不是 DecorVIew,這個可以去看看這篇文章:

Android View 繪製流程之 DecorView 與 ViewRootImpl

ViewRootImpl.invalidateChildInParent

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();  // 檢查是否是主執行緒
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {  // 從上面路徑來看,這裡是不可能為空的
            invalidate();  
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        // ... 跳過一段無關的程式碼
invalidateRectOnScreen(dirty); return null; }

這裡可以看出的是,最終會呼叫 invalidateRectOnScreen 方法。

ViewRootImpl.invalidateRectOnScreen

    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;

        // Add the new dirty rect to the current one 其實就是把兩個矩陣融合在一起
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        // Intersect with the bounds of the window to skip
        // updates that lie outside of the visible region
        final float appScale = mAttachInfo.mApplicationScale;
     // 主要就是檢查菊矩陣邊界對不對
final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
     // 邊界不對,就會直接置空
if (!intersected) { localDirty.setEmpty(); }
     // mWillDrawSoon 是當前是否馬上就要開始繪製了,如果開始繪製,就不去發起繪製了
if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } }

 invalidateRectOnScreen 主要是就是把 dirty 這個矩陣和已有的進行融合,然後再看看需不需要發起重新整理。

scheduleTraversals() 作用是將 performTraversals() 封裝到一個 Runnable 裡面,然後扔到 Choreographer 的待執行佇列裡,這些待執行的 Runnable 將會在最近的一個 16.6 ms 螢幕重新整理訊號到來的時候被執行。而 performTraversals() 是 View 的三大操作:測量、佈局、繪製的發起者。

小結:

當呼叫了 View.startAniamtion() 之後,動畫並沒有馬上就被執行,這個方法只是做了一些變數初始化操作,接著將 View 和 Animation 繫結起來,然後呼叫重繪請求操作,內部層層尋找 mParent,最終走到 ViewRootImpl 的 scheduleTraversals 裡發起一個遍歷 View 樹的請求,這個請求會在最近的一個螢幕重新整理訊號到來的時候被執行,呼叫 performTraversals 從根佈局 DecorView 開始遍歷 View 樹。

開始動畫

前面說到了動畫的開始最終是通過促發 View 繪製來形成的。此處不會再講 View 的繪製原理,不懂得可以看下面兩篇文章:

那麼在 View  繪製過程中,是在哪裡開始繪製的呢? 答案是 View 的 draw 方法裡面開始的。

但是這個 draw 不是我們自定義 view 時常見的 draw 方法,該 draw 方法有三個引數,是用於 View 自身繪製用的。

View.draw

該方法比較長,擷取部分來講:

/**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
     // 用於判斷是否支援硬體加速
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. * * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't * HW accelerated, it can't handle drawing RenderNodes. */ boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; boolean more = false; // ..... 跳過一些程式碼

     // 獲取之前儲存的 animation      final Animation a = getAnimation(); if (a != null) {
       // 不為空就說明是有動畫的 more
= applyLegacyAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; }
       // 這裡是拿到動畫引數,後面會再次講到 transformToApply
= parent.getChildTransformation(); } // ...... 省略程式碼 }

首先來看這個 draw 方法的三個引數:

  1. canvas:這個沒有什麼好分析的,是用來繪製用的

  2. parent:這個其實就是父 View,方便獲取一些資料;

  3. drawingTime:這個很重要,就是當前的繪製時間,後續做動畫的時候,會計算時間差,然後更新插值器;

一進到 draw 方法,就先獲取當前是否支援硬體加速。有硬體加速和沒有硬體加速走的是兩套邏輯。然後是獲取保之前儲存的 animation。

View.applyLegacyAnimation

接著呼叫 applyLegacyAnimation 開始處理動畫相關的邏輯。下面看下其方法內部的邏輯。

    /**
     * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
     * case of an active Animation being run on the view.
     */
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
     // 用於儲存此時重繪的變換 Transformation invalidationTransform;
final int flags = parent.mGroupFlags; final boolean initialized = a.isInitialized();
     // 判斷動畫有沒有開始初始化,沒有的化先進行初始化
if (!initialized) { a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight()); a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop); if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
       // 同時呼叫開始動畫回撥 onAnimationStart(); }      // 這裡是從父類中獲取當前的t,但是如果一個父類存在多個子 view 需要運動,那獲取的豈不是一樣了?其實每個子view 都會重新賦值,不會影響。
final Transformation t = parent.getChildTransformation();
     // 這裡是根據時間,t, 縮放因子來計算 t,這裡 t 是一個物件,在 animation 中進行賦值後,在這裡也可以用到
boolean more = a.getTransformation(drawingTime, t, 1f);
     // 對於需要縮放的子view,需要重新計算t,可是呼叫方法確是一樣的?那結果有啥不一樣嗎?這裡是為了將縮放和不縮放的 t 分出來
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { if (parent.mInvalidationTransformation == null) { parent.mInvalidationTransformation = new Transformation(); } invalidationTransform = parent.mInvalidationTransformation; a.getTransformation(drawingTime, invalidationTransform, 1f); } else { invalidationTransform = t; }      // more 為 true ,代表動畫還未結束 if (more) { if (!a.willChangeBounds()) { if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) == ViewGroup.FLAG_OPTIMIZE_INVALIDATE) { parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED; } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) { // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
            // 發起下一次重繪 parent.invalidate(mLeft, mTop, mRight, mBottom); } }
else { if (parent.mInvalidateRegion == null) { parent.mInvalidateRegion = new RectF(); } final RectF region = parent.mInvalidateRegion;
          // 對於會改變自己邊界的動畫,比如縮放,這時候需要計算當前縮放的尺寸範圍 a.getInvalidateRegion(
0, 0, mRight - mLeft, mBottom - mTop, region, invalidationTransform); // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;           // region 此時是更新尺寸後的範圍了 final int left = mLeft + (int) region.left; final int top = mTop + (int) region.top;
         // 發起下一次重繪 parent.invalidate(left, top, left
+ (int) (region.width() + .5f), top + (int) (region.height() + .5f)); } } return more; }

這個方法其實理解起來也很簡單,主要就是為了得到一個根據當前時間計算得到 Transformation 例項,裡面包含了下一次動畫所需要的資訊。

Transformation 裡面的內容如下:

Transformation

public class Transformation {
    /**
     * Indicates a transformation that has no effect (alpha = 1 and identity matrix.)
     */
    public static final int TYPE_IDENTITY = 0x0;
    /**
     * Indicates a transformation that applies an alpha only (uses an identity matrix.)
     */
    public static final int TYPE_ALPHA = 0x1;
    /**
     * Indicates a transformation that applies a matrix only (alpha = 1.)
     */
    public static final int TYPE_MATRIX = 0x2;
    /**
     * Indicates a transformation that applies an alpha and a matrix.
     */
    public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
   // 矩陣,控制縮放,平移,旋轉
    protected Matrix mMatrix;
   // 透明度
protected float mAlpha; protected int mTransformationType; private boolean mHasClipRect; private Rect mClipRect = new Rect(); // ...... 省略一大串程式碼 }

 

上述程式碼還省略很多方法,其實都是對矩陣的操作。

這裡提一下:Matrix 方法中的 setRotate() 方法會先清除該矩陣,即設為單位矩陣。之後設定旋轉操作的,同樣,setTranslate() 等方法也是一樣的。所以是不能疊加各種效果在一起的.如果是想多種效果同時使用的話,postRotate(),postTranslate()等類似的矩陣變換方法吧。

想進一步瞭解的可直接閱讀程式碼。

下面講下是如何獲取 Transformation 的。

Animation.getTransformation

    public boolean getTransformation(long currentTime, Transformation outTransformation) {
     // 等於-1,說明是剛開始動畫,記錄第一幀動畫時間
if (mStartTime == -1) { mStartTime = currentTime; }      // 相當於是延遲多少時間執行 final long startOffset = getStartOffset(); final long duration = mDuration; float normalizedTime; if (duration != 0) {
       // 歸一化,也就是轉化為百分比,當前動畫進度 normalizedTime
= ((float) (currentTime - (mStartTime + startOffset))) / (float) duration; } else { // time is a step-change with a zero duration normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f; } final boolean expired = normalizedTime >= 1.0f || isCanceled(); mMore = !expired;      // 確保動畫在 0-1 之間 if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { if (!mStarted) {
          // 通知動畫開始了。onAnimationStart 就是在這裡被呼叫 fireAnimationStart(); mStarted
= true; if (NoImagePreloadHolder.USE_CLOSEGUARD) { guard.open("cancel or detach or getTransformation"); } } if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); if (mCycleFlip) { normalizedTime = 1.0f - normalizedTime; }        // 根據進度獲取當前插值器的值 final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
       // 這裡 out 字首就是這個是要傳出去的,這個方法每個 Animation 子類都要自己實現,然後其實我們可以重寫這個方法,把進度傳出去你就知道當前動畫的進度了 applyTransformation(interpolatedTime, outTransformation); }      // 如果動畫被取消或者已經完成了
if (expired) { if (mRepeatCount == mRepeated || isCanceled()) { if (!mEnded) { mEnded = true; guard.close();
            // 這裡就是 onAnimationEnd 呼叫的地方 fireAnimationEnd(); } }
else {
          // else 說明動畫是重複的,這是需要計算重複次數,還有是不是無限迴圈的
if (mRepeatCount > 0) { mRepeated++; } if (mRepeatMode == REVERSE) { mCycleFlip = !mCycleFlip; } mStartTime = -1; mMore = true;           // 這裡就是 onAnimationRepeat 呼叫的地方 fireAnimationRepeat(); } } if (!mMore && mOneMoreTime) { mOneMoreTime = false; return true; } return mMore; }

 

getTransformation 主要就是管理動畫狀態的。到底是開始(記錄開始時間),還是正在進行(計算進度),還是已經結束了(通知結束了)。

其中呼叫的 applyTransformation,每個 Animation 子類都要自己實現,然後其實我們可以重寫這個方法,把進度傳出去你就知道當前動畫的進度了。子類其實是把最後的計算結果儲存在 Transformation 裡面了,這樣就拿到了下一幀動畫引數。

還有大家平時用到的 AnimationListener 也是在這裡進行通知回撥的。

那拿到 Transformation 後,是怎麼用的呢,這個就得 回到 view.draw 方法了。

view.draw

前面講到了 Transformation 其實是從 parent 中獲取,賦值給 transformToApply;

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {   
         // ...... 省略一大部分程式碼
        float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
     // 下面這個if 會進入動畫的真正的繪製時期
if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; int transY = 0; if (offsetForScroll) { transX = -sx; transY = -sy; } if (transformToApply != null) { if (concatMatrix) {
               // 為TRUE,代表是使用硬體加速來進行繪製
if (drawingWithRenderNode) { renderNode.setAnimationMatrix(transformToApply.getMatrix()); } else { // Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result. canvas.translate(-transX, -transY); canvas.concat(transformToApply.getMatrix()); canvas.translate(transX, transY); } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } float transformAlpha = transformToApply.getAlpha();
            // 下面是關於透明度的動畫
if (transformAlpha < 1) { alpha *= transformAlpha; parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } } if (!childHasIdentityMatrix && !drawingWithRenderNode) { canvas.translate(-transX, -transY); canvas.concat(getMatrix()); canvas.translate(transX, transY); } } // Deal with alpha if it is or used to be <1 if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) { if (alpha < 1) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA; } else { mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA; } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; if (!drawingWithDrawingCache) { final int multipliedAlpha = (int) (255 * alpha); if (!onSetAlpha(multipliedAlpha)) { if (drawingWithRenderNode) { renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha()); } else if (layerType == LAYER_TYPE_NONE) { canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(), multipliedAlpha); } } else { // Alpha is handled by the child directly, clobber the layer's alpha mPrivateFlags |= PFLAG_ALPHA_SET; } } } } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { onSetAlpha(255); mPrivateFlags &= ~PFLAG_ALPHA_SET; }

那麼對於 AnimationSet 又是如何處理的呢?

首先他也是繼承了了 Animation,其次,它有個陣列裝門用來存放 Animation 集合。也是通過  getTransformation 來獲取Transformation的。

AnimationSet.

    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);

            started = started || a.hasStarted();
            ended = a.hasEnded() && ended;
        }

        if (started && !mStarted) {
            dispatchAnimationStart();
            mStarted = true;
        }

        if (ended != mEnded) {
            dispatchAnimationEnd();
            mEnded = ended;
        }

        return more;
    }

通過 for 迴圈,依次獲取對應的 annimation 的矩陣,然後再將矩陣效果合到一起。

到此,對於 動畫應該是有自己的認識了。 View Animation 的整個執行邏輯也就講完了。

 

總結

那麼這裡回答開頭的三個問題:

  • 為什麼移動位置後,點選事件的響應依舊是在原來位置上?

因為動畫是在 draw 時候形成的,也就是說只是視覺效果。其並沒有改變它本身在父類中的位置;

  • 如果想知道動畫的執行進度,是如何獲取呢?

繼承 Animation 對應的子類,然後重寫 applyTransformation 方法,就可以從中獲取到進度。

  • 如果對 View 做放大縮小得動畫,那麼其寬度高度值是否會變化。

動畫發生在 draw 時期,並不會改變測量結果

 

View Animation 是在繪製的時候,改變 view 的視覺效果來實現動畫的。所以不會對 view 的測量和佈局過程有影響。

View 的動畫是通過觸發繪製過程來執行 draw 的。因為動畫是連續的,所以需要不停的觸發。

 

 

參考文章:

View 動畫 Animation 執行原理解析

 

 

相關文章