1. 程式人生 > >Android Animation動畫原理原始碼分析

Android Animation動畫原理原始碼分析

Android 平臺提供了三類動畫,一類是 Tween 動畫-Animation,即通過對場景裡的物件不斷做影象變換 ( 平移、縮放、旋轉 ) 產生動畫效果;第二類是 Frame 動畫,即順序播放事先做好的影象,跟電影類似。最後一種就是3.0之後才出現的屬性動畫PropertyAnimator ,這個分享的是第一類動畫原理。

Animation動畫有4種,TranslateAnimation、ScaleAnimation、RotateAnimation、AlphAnimation,其都繼承了Animation這個抽象類,實現了applyTransformation(float interpolatedTime, Transformation t)函式,如下ScaleAnimation的實現:

根據函式的命名來看就是對Transformation進行設定,通過傳來interpolatedTime浮點值不斷改變Transformation的矩陣Matrix來實現動畫的縮放。那這個函式什麼時候被呼叫,如何被呼叫,一步步來分析下:

可以看到在Animation中getTransformation(long currentTime, Transformation outTransformation)進行了呼叫,並且判斷了動畫是否繼續下去,還有其他變化幀是否完成,如下:
  public boolean getTransformation(long currentTime, Transformation outTransformation) {
    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; if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { if (!mStarted) { 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); //對transformation進行改變 applyTransformation(interpolatedTime, outTransformation); } ******省略程式碼****** if (!mMore && mOneMoreTime) { mOneMoreTime = false; return true; } return mMore; }

那getTransformation函式又是在哪裡被呼叫呢。我們一步步從動畫api使用源頭開始分析:
我們知道使用Animation執行動畫從view.startAnimation(Animation animation)開始,其實現如下:

public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}

一看沒有啥,就是設定賦值以及清父View Caches的標籤設定,沒有看出啥是如何讓動畫執行起來,呼叫了Animation的getTransformation函式。一般會說看到了invalidate函式呼叫了,就是重新整理了View,那具體是怎麼重新整理View繪製的呢,那先講下invalidate(true)執行流程和邏輯是啥,到底怎麼樣導致了重新整理。沒有別的辦法,看原始碼,gogogo……詳見我的另一遍文章《Android invalidate()和postInvalidate()重新整理原理》

1、invalidate最後呼叫到invalidateInternal函式,把view的相對尺寸和相關狀態設定傳遞

   void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom -  mTop, invalidateCache, true);
      }
 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate)

2、invalidateInternal函式中邏輯不少,主要部分如下,有段呼叫父view進行重新整理:

        // Propagate the damage rectangle to the parent view.
     final AttachInfo ai = mAttachInfo;
     final ViewParent p = mParent;
     if (p != null && ai != null && l < r && t < b) {
     final Rect damage = ai.mTmpInvalRect;
     damage.set(l, t, r, b);
     p.invalidateChild(this, damage);
    }

那ViewParent.invalidateChild實現是在哪裡呢,猜到ViewGroup,發現其實現了ViewParent的介面,並且在ViewGroup.addView時新增子View的邏輯中會最後呼叫到`addViewInner方法:

 private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

       ......

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (mTransition != null) {
            mTransition.addChild(this, child);
        }

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

        if (index < 0) {
            index = mChildrenCount;
        }

        addInArray(child, index);

        // tell our children
        //設定父view
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }

        if (child.hasFocus()) {
            requestChildFocus(child, child.findFocus());
        }

     .....
    }

對child的ViewPrarent變數mParent進行賦值,把自己傳遞給子View,那麼定位到ViewGroup對invalidateChild方法實現,發現裡面有迴圈查詢父View邏輯,如下:

do {
    View view = null;
    if (parent instanceof View) {
        view = (View) parent;
    }


   **********
         省略程式碼
    // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
    **********
    parent = parent.invalidateChildInParent(location, dirty);
    if (view != null) {
        // Account for transform on current parent
       ********
    }
} while(parent!=null)

這個迴圈直到parent!=null才停止,那什麼時候parent.invalidateChildInParent(location, dirty)會返回null呢。這裡就要分析View樹形結構了,佈局結構中最上層的ViewParent是誰,什麼時候賦值的。這個要從setContentView函式設定佈局檔案開始講,有點長,但也許都是到佈局的最頂層view就是DecorView(可以查原始碼),那DecorView的ViewParent又是誰,這個就必須從新增decorView定位。參考http://blog.csdn.net/luoshengyang/article/details/6689748這邊老羅的文章,分析應用啟動以及View顯示載入過程,從中可以知道在AMS通知PerformResumeActivity命令時開始顯示介面,會呼叫activity的makeVisible(),該函式添加了mDecor(DecorView)

 void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
 }

ViewManager.addView的實現在WindowManagerGlobal的addView方法中:

會看到一個ViewRootImpl,看起來很想最最頂層 的View,查詢原始碼發現:併發View,但是其實現了ViewParent,這就差不多連起來了,然後其呼叫了setView方法檢視實現如下

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;           

                ************
               很多程式碼省略
                *************
                view.assignParent(this);
                mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
                mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

                 ************
                 很多程式碼省略
                *************
            }
        }
    }

發現 mView = view進行賦值,並呼叫assignParent(this),把這個ViewParent實現付給DecorView,從持有父View。既然ViewRootImp實現了ViewParent,並且付給了DecorView,那在DecorView查詢父parent時(parent.invalidateChildInParent(location, dirty))就可以定位到ViewRootImpl實現了,找到ViewRootImp.invalidateChildInParent:發現終於返回了null,結束了迴圈。

 @Override
 public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);


          if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        invalidateRectOnScreen(dirty);

        return null;
    }

invalidateRectOnScreen(dirty);又呼叫了誰,繼續定位

可以查原始碼分析就不一步步寫了,schuduleTraversals()呼叫是固定最後呼叫到performTraversals函式,這個是view繪製的源頭開始處,程式碼很多,裡面呼叫了mView.measure() mView.layout 、mView.draw等方法。終於差不多看到希望了,mView.draw()就是呼叫了DecorView的draw(),然後就把這個佈局遍歷繪製了一遍。那麼走到執行動畫的View的其父View繪製draw方法時候,會走到dispatchDraw,在View.draw(canvas)裡面按步驟分別回執如下1、2、3、4、5、6步,其中有dispatchDraw來繪製子View。

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;


    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

ViewGroup過載了dispatchDraw,實現繪製子View的內容,原始碼如下:

protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
    //執行佈局動畫
    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate(){
        final boolean buildCache = !isHardwareAccelerated();
        for (int i = 0; i < childrenCount; i++) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                final LayoutParams params = child.getLayoutParams();
                attachLayoutAnimationParameters(child, params, i, childrenCount);
                bindLayoutAnimation(child);
            }
        }
        final LayoutAnimationController controller = mLayoutAnimationController;
        if (controller.willOverlap()) {
            mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
        }
        controller.start();
        mGroupFlags &= ~FLAG_RUN_ANIMATION;
        mGroupFlags &= ~FLAG_ANIMATION_DONE;


        if (mAnimationListener != null) {
            mAnimationListener.onAnimationStart(controller.getAnimation());
        }
    }
     *******省略部分程式碼 *********
    // We will draw our child's animation, let's reset the flag


    //more為true表示動畫沒有執行完
    boolean more = false;
    final long drawingTime = getDrawingTime();
    *******省略部分程式碼 *********
    for (int i = 0; i < childrenCount; i++) {
         *******省略部分程式碼 *********
        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        final View child = (preorderedList == null) ? children[childIndex] :
                preorderedList.get(childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            //繪製子View,直接呼叫了child.draw(canvas, this(ViewParent), drawingTime);
            // 動畫真正執行地方
            more |= drawChild(canvas, child, drawingTime);
        }
    }


    *******省略部分程式碼 *********
    // mGroupFlags might have been updated by drawChild()
    flags = mGroupFlags;
    if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
        invalidate(true);
    }
        *******省略部分程式碼 *********
}

看到呼叫child.draw(Canvas canvas, ViewGroup parent, long drawingTime),這個draw(canvas)函式不一樣,不過前者又會呼叫到後者。看下child.draw(Canvas canvas, ViewGroup parent, long drawingTime)是如何執行動畫呼叫到animattion.applyTransformation()對Matrix進行矩陣變化,原始碼如下:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {


    ****************省略部分程式碼 ***************
    boolean more = false;
    Transformation transformToApply = null;
    boolean concatMatrix = false;
    final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
    final Animation a = getAnimation();//獲取ziView的動畫,在View.startAnimation是就開始賦值了。
    if (a != null) {
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        transformToApply = parent.getChildTransformation();// a.applyLegacyAnimation進行賦值
    } else {
        ****************省略程式碼 ***************
    }
    ****************省略程式碼 ***************
    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) {
                    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.concat(transformToApply.getMatrix());//傳遞給canvas進行矩陣實現動畫
                        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);
            }
        }
          ****************省略程式碼 ***************
        return more;
    }

在這麼程式碼中動畫animation判斷不為空之後,進入more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);,這裡面傳入了animation,有沒有對動畫執行操作呢,如下原始碼:

private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
                                     Animation a, boolean scalingRequired) {
    Transformation invalidationTransform;
    *********省略部分程式碼 **********
    final Transformation t = parent.getChildTransformation();
    boolean more = a.getTransformation(drawingTime, t, 1f);///呼叫了Animation的
    ///getTransformation方法,這個方法進而呼叫了applyTransformation
    if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
        if (parent.mInvalidationTransformation == null) {
            parent.mInvalidationTransformation = new Transformation();
        }
        invalidationTransform = parent.mInvalidationTransformation;
        a.getTransformation(drawingTime, invalidationTransform, 1f);
    } else {
        invalidationTransform = t;
    }
    if (more) {//為true又重新重新整理,呼叫viewparent.invalidate
        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;
            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;
}

從 view.applyLegacyAnimation()原始碼可以看到呼叫了animation.getChildTransformation()並通過返回的boolean值覆蓋more變數,並且同時把對傳入animation.getTransformation(drawingTime, t, 1f)中的transformToApply(parent.getChildTransformation()獲取)進行變化矩陣,當more為true,看到又呼叫父View parent.invalidate(),重新重新整理view樹進行再一次繪製剩餘動畫內容。當applyLegacyAnimation執行完成之後draw(Canvas canvas, ViewGroup parent, long drawingTime)裡面繼續transformToApply = parent.getChildTransformation()獲取變化之後transformToApply,若不為空,又傳遞給canvas.concat(transformToApply.getMatrix())來對canvas進行矩陣變化操作,從而對繪製的view內容發生動畫。

到這裡就講完了view tween動畫的執行原理