1. 程式人生 > >二、View Animation動畫原始碼簡析——動畫的啟動執行

二、View Animation動畫原始碼簡析——動畫的啟動執行

不知道大夥有沒有想過,當我們呼叫了 View.startAnimation(animation) 之後,動畫是不是馬上就開始執行了?
——我們先來看看 View.startAnimation(animation) 方法裡都做那那些事情。

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

首先一個的animation.setStartTime(Animation.START_ON_FIRST_FRAME)方法:

public void setStartTime(long startTimeMillis) {
    mStartTime = startTimeMillis;
    mStarted = mEnded = false;
    mCycleFlip = false;
    mRepeated = 0;
    mMore = true;
}

這裡setStartTime()方法主要就是完成動畫相關引數的賦值操作。
再來看setAnimation(animation)方法:

public void setAnimation(Animation 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();
    }
}

三行註釋的翻譯:如果螢幕是關閉的,假設動畫開始時間現在,而不是未來我們畫。保持START_ON_FIRST_FRAME開始時間將導致動畫開始當螢幕。

if條件語句裡面有三個條件,依次是:
mAttachInfo != null————當AttachInfo物件 mAttachInfo不為空,這個好說。
mAttachInfo.mDisplayState == Display.STATE_OFF————AttachInfo.class下的mDisplayState = Display.STATE_UNKNOWN,我們可以查詢到:Display.class下的STATE_UNKNOWN變數狀態:

public static final int STATE_UNKNOWN = 0;

而Display.class下的STATE_OFF變數狀態為:

public static final int STATE_OFF = 1;

所以二者永遠不可能相等,直接為false,那麼後面的一個條件可以不用判斷了。這樣if條件語句經“&&”後衛false,裡面的語句不會執行。那就只剩下animation.reset();一條了。他的原始碼如下:

public void reset() {
    mPreviousRegion.setEmpty();
    mPreviousTransformation.clear();
    mInitialized = false;
    mCycleFlip = false;
    mRepeated = 0;
    mMore = true;
    mOneMoreTime = true;
    mListenerHandler = null;
}

可以看得出就是重置animation動畫所需要的各種引數。

然後我們再看startAnimation()方法的invalidateParentCaches()方法:

protected void invalidateParentCaches() {
    if (mParent instanceof View) {
        ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
    }
}

這裡對mPrivateFlags 標記做了一次位或運算。

最後,我們再來看看invalidate(true);方法:

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

這裡直接呼叫了invalidateInternal方法,從字面意思可以看得出他就是重新整理UI的。其中,(0, 0)為View的座標原點,(mRight - mLeft, mBottom - mTop)分別為View的寬度和高度,二者合起來就是確定要重新整理的View的座標位置。

我們進入invalidateInternal()方法的原始碼看看:

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
    if (skipInvalidate()) {
        return;
    }
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }
        mPrivateFlags |= PFLAG_DIRTY;
        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }
        // Propagate the damage rectangle to the parent view.
        final AttachInfo ai = mAttachInfo;
        //mParent 一般是 ViewGroup,下面其實是呼叫了 ViewGroup 的 invalidateChild()
        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);
        }
        // Damage the entire projection receiver, if necessary.
        if (mBackground != null && mBackground.isProjected()) {
            final View receiver = getProjectionReceiver();
            if (receiver != null) {
                receiver.damageInParent();
            }
        }
    }
}

程式碼量稍大,不過我們撿緊要的看:

if (mGhostView != null) {
    mGhostView.invalidate(true);
    return;
}
if (skipInvalidate()) {
    return;
}

這兩個條件語句,在方法開始的時候就執行不滿足條件就return。當真是return不要錢不值錢啊,還沒執行什麼程式碼任務就return了。所以我們也無關痛癢的忽略它的存在。然後繼續往下看。
可以發現真個invalidateInternal方法的核心就是呼叫了parent(ViewGroup )的 invalidateChild()方法和receiver.damageInParent()方法。我們先跟進invalidateChild()去看看:

//ViewGroup#invalidateChild()  
public final void invalidateChild(View child, final Rect dirty) {
    //1. 注意這裡,下面的do{}while()迴圈會用到
    ViewParent parent = this;
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        ...
        do {
        ...  
        //2.第一次迴圈的時候 parent 是 this 即 ViewGroup 本身,迴圈終止條件是 parent == null,所以可以猜測這個方法會返回當前ViewGroup的parent,跟進確認一下
        parent = parent.invalidateChildInParent(location, dirty);
        ...
        } while (parent != null);
    }
}

這裡有一個 do{}while() 的迴圈操作,第一次迴圈的時候 parent 是 this,即 ViewGroup 本身,所以接下去就是呼叫 ViewGroup 本身的 invalidateChildInParent() 方法,然後迴圈終止條件是 patent == null,所以這個方法返回的應該是 ViewGroup 的 parent,跳進去看看其原始碼:

//ViewGroup#invalidateChildInParent()  
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    //1.當滿足if條件時,就會返回 mParent,否則返回 null。
    if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
            (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
        if (...) {
            ...
            return mParent; 
        } else {
            ...
            return mParent;
        }
    }
    return null;
}

發現關鍵是 PFLAG_DRAWN 和 PFLAG_DRAWING_CACHE_VALID 這兩個是什麼時候賦值給 mPrivateFlags,因為只要有兩個標誌中的一個時,該方法就會返回 mParent,一個具體的 View 的 mParent 是 ViewGroup,ViewGroup 的 mParent 還是是 ViewGoup,所以在 do{}while() 迴圈裡會一直不斷的尋找 mParent,而一個 View 樹最頂端的 mParent 是 ViewRootImpl,所以最終是會走到了 ViewRootImpl 的 invalidateChildInParent() 裡去。

至於一個介面的 View 樹最頂端為什麼是 ViewRootImpl,這個就跟 Activity 啟動過程有關了。我們都清楚,在 onCreate 裡 setContentView() 的時候,是將我們自己寫的佈局檔案新增到以 DecorView 為根佈局的一個 ViewGroup 裡,也就是說 DevorView 才是 View 樹的根佈局,那為什麼又說 View 樹最頂端其實是 ViewRootImpl 呢?

這是因為在 onResume() 執行完後,WindowManager 將會執行 addView(),然後在這裡面會去建立一個 ViewRootImpl 物件,接著將 DecorView 跟 ViewRootImpl 物件繫結起來,並且將 DecorView 的 mParent 設定成 ViewRootImpl,而 ViewRootImpl 是實現了 ViewParent 介面的,所以雖然 ViewRootImpl 沒有繼承 View 或 ViewGroup,但它確實是 DecorView 的 parent。這部分內容應該屬於 Activity 的啟動過程相關原理的,這裡不深入了。

那麼我們繼續返回到尋找動畫執行的地方,我們跟到了 ViewRootImpl 的 invalidateChildInParent() 裡去了,看看它做了些什麼:

//ViewRootImpl#invalidateChildInParent()  
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    ...
    if (dirty == null) {
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }
    ...
    invalidateRectOnScreen(dirty);
    return null;
}

它的所有返回值都是 null,所以之前那個 do{}while() 迴圈最終就是執行到這裡後肯定就會停止了。然後引數 dirty 是在最初 View 的 invalidateInternal() 裡層層傳遞過來的,可以肯定的是它不為空,也不是 isEmpty,所以繼續跟到 invalidateRectOnScreen() 方法裡看看:

private void invalidateRectOnScreen(Rect dirty) {
    ...
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        //1.跟到這裡看到這個方法就可以停住了
        scheduleTraversals();
    }
}

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

View 樹裡面不管哪個 View 發起了佈局請求、繪製請求,統統最終都會走到 ViewRootImpl 裡的 scheduleTraversals(),然後在最近的一個螢幕重新整理訊號到了的時候再通過 ViewRootImpl 的 performTraversals() 從根佈局 DecorView 開始依次遍歷 View 樹去執行測量、佈局、繪製三大操作。這也是為什麼一直要求頁面佈局層次不能太深,因為每一次的頁面重新整理都會先走到 ViewRootImpl 裡,然後再層層遍歷到具體發生改變的 View 裡去執行相應的佈局或繪製操作。

我們從 View.startAnimation() 開始跟進原始碼分析的這一過程中,也可以看出,執行動畫,其實內部會呼叫 View 的重繪請求操作 invalidate() ,所以最終會走到 ViewRootImpl 的 scheduleTraversals(),然後在下一個螢幕重新整理訊號到的時候去遍歷 View 樹重新整理螢幕。

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

那麼Animation動畫到底是從哪裡開始執行的呢?

View的繪製流程中,測量、佈局、繪製,View 顯示到螢幕上的三個基本操作都是由 ViewRootImpl 的 performTraversals() 來控制,而作為 View 樹最頂端的 parent,要控制這顆 Veiw 樹的三個基本操作,只能通過層層遍歷。所以,測量、佈局、繪製三個基本操作的執行都會是一次遍歷操作。

繪製流程的開始是由 ViewRootImpl 發起的,然後從 DecorView 開始遍歷 View 樹。而遍歷的實現,是在 View的draw() 方法裡的。

public void draw(Canvas canvas) {
     ...
    //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 //呼叫onDraw()繪製自己
    //4. Draw children       //呼叫dispatchDraw()繪製子View
    // 5. If necessary, draw the fading edges and restore layers
    // 6. Draw decorations (scrollbars for instance)

    1. Draw the background
    //Step 1, draw the background, if needed
    if (!dirtyOpaque) {    
        drawBackground(canvas);
    }
    2. If necessary, save the canvas' layers to prepare for fading
    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        3. Draw view's content
        // Step 3, draw the content
        if (!dirtyOpaque) {
             onDraw(canvas); 
        }
        4. Draw children
        // Step 4, draw the children
        dispatchDraw(canvas);       drawAutofilledHighlight(canvas);
        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
        5. If necessary, draw the fading edges and restore layers
        6. Draw decorations (scrollbars for instance)

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
        if (debugDraw()) {    
            debugDrawFocus(canvas);  
         }
    }
}

這個方法裡主要做了上述六件事,大體上就是如果當前 View 需要繪製,就會去呼叫自己的 onDraw(),然後如果有子 View,就會呼叫dispatchDraw() 將繪製事件通知給子 View。ViewGroup 重寫了 dispatchDraw(),呼叫了 drawChild(),而 drawChild() 呼叫了子 View 的 draw(Canvas, ViewGroup, long),而這個方法又會去呼叫到 draw(Canvas) 方法,層層迴圈遍歷。

在draw(Canvas, ViewGroup, long) 裡面,發現了與動畫相關的程式碼:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...
    boolean more = false;
    ...
    //1.獲取View繫結的動畫
    final Animation a = getAnimation();
    if (a != null) {
        //2.如果View有繫結動畫,執行動畫相關邏輯
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        ...
    } 
    ...
    return more;
}

而在前面我們提到了startAnimation(animation)方法裡面的的操作,其中第二個方法就是賦值:

setAnimation(animation);

setAnimation(animation)賦值操作中,其中一個賦值操作是:

mCurrentAnimation = animation;

而這裡的getAnimation()則是獲取前面賦值進去的animation物件——mCurrentAnimation:

public Animation getAnimation() {
        return mCurrentAnimation;
}

這驗證了我們上面的結論。所以緊隨其後的if判斷語句就是動畫的執行開始的地方:

if (a != null) {
    //這裡開始執行動畫。
    more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
    ...
} 

我們進入applyLegacyAnimation()方法 IM去看看:

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();
    }
    final Transformation t = parent.getChildTransformation();
    //獲取返回值來檢視動畫是否已經結束
    boolean more = a.getTransformation(drawingTime, t, 1f);
    if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
        if (parent.mInvalidationTransformation == null) {
            parent.mInvalidationTransformation = new Transformation();
        }
        invalidationTransform = parent.mInvalidationTransformation;
        //呼叫動畫的getTransfomation()計算動畫
        a.getTransformation(drawingTime, invalidationTransform, 1f);
    } else {
        invalidationTransform = t;
    }
    //動畫還沒結束時
    if (more) {
        //除了 Alpha 動畫預設返回 false,其餘基礎動畫都返回 true
        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) {
                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);
            parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;

            final int left = mLeft + (int) region.left;
            final int top = mTop + (int) region.top;
            //呼叫mParent的請求重繪操作,這個方法在開頭分析startAnimation時分析過了
            parent.invalidate(left, top, left + (int) (region.width() + .5f), top + (int) (region.height() + .5f));
        }
    }
    return more;
}

程式碼量又開始大起來了,我們還是撿緊要的看。

首先我們就能注意到第一個if條件語句裡面就有這麼一句:onAnimationStart(),不用我說你也能猜到他是幹什麼的了。
然後Transformation物件的獲取以及動畫的核心程式碼:animation.getTransformation(drawingTime, t, 1f);

public boolean getTransformation(long currentTime, Transformation outTransformation, float scale) {
    mScaleFactor = scale;
    return getTransformation(currentTime, outTransformation);
}

這裡先是做了賦值,然後呼叫getTransformation方法:

public boolean getTransformation(long currentTime, Transformation outTransformation) {
    //記錄動畫第一幀時間
    if (mStartTime == -1) {
        mStartTime = currentTime;
    }
    //動畫延遲開始時間,預設為0
    final long startOffset = getStartOffset();
    //動畫持續時間
    final long duration = mDuration;
    float normalizedTime;
    if (duration != 0) {
        //計算動畫的進度:(當前時間 - 動畫第一幀時間mStartTime - 動畫延遲開始時間startOffset ) / 動畫持續時間duration 
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / (float) duration;
    } else {
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }
    //expired = true 表示動畫已經結束或者被取消了
    final boolean expired = normalizedTime >= 1.0f || isCanceled();
    mMore = !expired;
    //確保動畫進度在 0.0 ---- 1.0 之間
    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);
        //根據動畫進度匹配動畫效果
        applyTransformation(interpolatedTime, outTransformation);
    }
    //如果設定了動畫迴圈的次數,那麼當動畫結束的時候判斷一下迴圈次數是否已經達到設定的次數,沒有的話,重置第一幀時間
    if (expired) {
        if (mRepeatCount == mRepeated || isCanceled()) {
            if (!mEnded) {
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
            if (mRepeatCount > 0) {
                mRepeated++;
            }
            if (mRepeatMode == REVERSE) {
                mCycleFlip = !mCycleFlip;
            }
            mStartTime = -1;
            mMore = true;
            fireAnimationRepeat();
        }
    }
    //綜上,mMore = true 表示動畫進度還沒超過 1.0,也就是動畫還沒結束;false 表示動畫已經結束或者被取消了
    if (!mMore && mOneMoreTime) {
        mOneMoreTime = false;
        return true;
    }
    return mMore;
}

程式碼量稍大,還是老規矩,撿緊要的看。
該方法主要做了兩件事情,一是計算動畫所需要的時間資料;二是呼叫applyTransformation()方法匹配動畫效果。而applyTransformation() 最終是在繪製流程中的 draw() 過程中執行到的,在每一幀的螢幕重新整理訊號來的時候,遍歷 View 樹並重新渲染View介面,動畫只是在這個過程中順便執行的。

而getTransformation() 的返回值代表的是動畫是否完成。他在applyLegacyAnimation() 方法裡面被呼叫,這個前面已經提及到,就不再展示程式碼了。getTransformation()方法和 applyLegacyAnimation()方法關鍵地方都有註釋。

當動畫如果還沒執行完,就會再呼叫 invalidate() 方法,層層通知到 ViewRootImpl 再次發起一次遍歷請求,當下一幀螢幕重新整理訊號來的時候,再通過 performTraversals() 遍歷 View 樹繪製時,該 View 的 draw 收到通知被呼叫時,會再次去呼叫 applyLegacyAnimation() 方法去執行動畫相關操作,包括呼叫 getTransformation() 計算動畫進度,呼叫 applyTransformation() 應用動畫。
————簡單來說就是:正常情況下,UI是以幀/16.6ms的頻率重新整理UI,執行一次 applyTransformation(),直到動畫完成。所以 applyTransformation() 將會被回撥多次,且呼叫次數是由動畫的持續時間以及重複執行次數來決定的,無法人為進行設定。

總結一下:
首先,當呼叫了 View.startAnimation() 時動畫並沒有馬上就執行,而是通過 invalidate() 層層通知到 ViewRootImpl 發起一次遍歷 View 樹的請求,而這次請求會等到接收到最近一幀到了的訊號時才去發起遍歷 View 樹繪製操作。

其次,從 DecorView根目錄 開始遍歷,遍歷時會呼叫到 View 的 draw() 方法,如果 View 有繫結動畫,那麼會去呼叫applyLegacyAnimation()方法,該方法是專門處理動畫相關邏輯的。

然後,在 applyLegacyAnimation() 這個方法裡,如果動畫物件znimation還沒有執行過初始化,先呼叫動畫的初始化方法 initialized(),同時呼叫 onAnimationStart() 通知動畫開始了,然後呼叫 getTransformation() 來根據當前時間計算動畫進度,緊接著呼叫 applyTransformation() 並傳入動畫進度來應用動畫。

再後,根據getTransformation() 方法的返回值,判斷動畫是否已經結束:如果沒結束就返回 true,已經結束或者被取消了就返回 false。所以 applyLegacyAnimation() 會根據 getTransformation() 的返回值來決定是否通知 ViewRootImpl 再發起一次遍歷請求,返回值是 true 表示動畫沒結束,那麼就去通知 ViewRootImpl 再次發起一次遍歷請求。然後當下一幀到來時,再從 DecorView 開始遍歷 View 樹繪製,重複上面的步驟,這樣直到動畫結束。

最後,最重要的一點:動畫並不是單獨執行的,是在View的每一幀的繪製流程裡被執行,也就是說,如果這一幀裡有一些 View 需要重繪,那麼這些工作同樣是在這一幀裡的這次遍歷 View 樹的過程中完成的。每一幀只會發起一次 perfromTraversals() 操作。

相關推薦

View Animation動畫原始碼——動畫啟動執行

不知道大夥有沒有想過,當我們呼叫了 View.startAnimation(animation) 之後,動畫是不是馬上就開始執行了? ——我們先來看看 View.startAnimation(animation) 方法裡都做那那些事情。 public voi

ffmpeg原始碼)av_register_all(),avcodec_register_all()

av_register_all() 該函式在所有基於ffmpeg的應用程式中幾乎都是第一個被呼叫的。只有呼叫了該函式,才能使用複用器,編碼器等。 av_register_all()呼叫了avcodec_register_all()。avcodec_regis

併發系列()——FutureTask類原始碼

背景   本文基於JDK 11,主要介紹FutureTask類中的run()、get()和cancel() 方法,沒有過多解析相應interface中的註釋,但閱讀原始碼時建議先閱讀註釋,明白方法的主要的功能,再去看原始碼會更快。   文中若有不正確的地方歡迎大夥留言指出,謝謝了! 1、FutureTask類

JAVA8的IO流原始碼

讓我們來分析一下java8裡面的IO原始碼。 一般來說分兩類,即位元組流和字元流,通過下面的思維導向圖總結下: 關於流有幾點是要注意的: 第一,讀寫流要及時的關閉,使用close方法。 第二,深入理解read和readline的區別。具體請看下面的原始碼: 需要注意

ffmpeg原始碼(十三)ffmpeg API變更 2009-03-01—— 2017-05-09變更

The last version increases were: libavcodec: 2015-08-28 libavdevice: 2015-08-28 libavfilter: 2015-08-28 libavformat: 2015-08-28 libavresample: 201

ffmpeg原始碼(一)結構總覽

未畢業通過校招進入了某做機的公司從事camera方面的工作。比較悲劇的是做了將近一年的Camera之後,正要研究Camera上下層打通任督二脈的時候,公司架構調整加上OS版本大變動,被調到了多媒體組(不過也好,我對編碼解碼這塊也是嚮往已久)。以前大學的時候用vi

Hadoop之job提交流程原始碼

1. 進入Job提交方法 public boolean waitForCompletion(boolean verbose               

SparseArray詳解及原始碼

一、前言 SparseArray 是 Android 在 Android SdK 為我們提供的一個基礎的資料結構,其功能類似於 HashMap。與 HashMap 不同的是它的 Key 只能是 int 值,不能是其他的型別。 二、程式碼分析 1. demo 及其簡析 首先也還是先通過 demo 來看一

MJPG-streamer原始碼

  MJPG-streamer主體上是由main函式和輸入外掛、輸出外掛組成。   軟體執行的流程是先對攝像頭進行初始化然後設定基本的輸入輸出引數,接著從攝像頭中獲取資料放到全域性記憶體中,然後通知輸出函式來取出,接著輸出。   攝像頭的初始化由結構體vdIn來進行

ffmpeg原始碼(九)av_log(),AVClass,AVOption

1.av_log() av_log()是FFmpeg中輸出日誌的函式。隨便開啟一個FFmpeg的原始碼檔案,就會發現其中遍佈著av_log()函式。一般情況下FFmpeg類庫的原始碼中是不允許使用printf()這種的函式的,所有的輸出一律使用av_log()

解析 | openshift原始碼之pod網路配置(下)

【編者按】openshift底層是通過kubelet來管理pod,kubelet通過CNI外掛來配置pod網路.openshift node節點在啟動的時會在一個goroutine中啟動kubelet, 由kubelet來負責pod的管理工作。 本文主要從原始碼的角度

搜尋引擎收錄抓取排序頁面的原理

豈論是百度仍是谷歌,豈論是360照常搜狗,都有自身對應的蜘蛛,每一個徵採引擎,都有本身稀罕的演算法,固然,最關健的照常看baidu與google了,部落發明,通常環境下,只有百度徵採上您的某個關健詞有排名,那末國際其它的幾個搜尋引擎,基本都市有排名,無非,網站頁面的收錄、抓取是有所一致的.關於一個SEOr

ElementUI 原始碼——原始碼結構篇

ElementUI 作為當前運用的最廣的 Vue PC 端元件庫,很多 Vue 元件庫的架構都是參照 ElementUI 做的。作為一個有夢想的前端(鹹魚),當然需要好好學習一番這套比較成熟的架構。 目錄結構解析 首先,我們先來看看 ElementUI 的目錄結構,總體來說,ElementUI 的目錄結構與

vuex原始碼

前言 基於 vuex 3.1.2 按如下流程進行分析: Vue.use(Vuex); const store = new Vuex.Store({ actions, getters, state, mutations, modules // ... });

Android Studio 開啟原始碼專案,配置啟動執行

開啟下載的原始碼時,執行裡是空的,配置過程記錄如下: 點選左上角的“+”,新增Android App 填寫Name,下拉列表中選擇Module 這時會發現下面有個Error提示: Messages中也出現提示: 點選 Upgrade plugin to ve

Android三種動畫View Animation(補間動畫) Drawable Animation(幀動畫) Property Animation(屬性動畫)(下)

轉載:http://blog.csdn.net/lmj623565791/article/details/38092093 三種動畫的優缺點: (1)Frame Animation(幀動畫)主要用於播放一幀幀準備好的圖片,類似GIF圖片,優點是使用簡單

Linux C程式設計---指標陣列維陣列多級指標)

講到指標和陣列,先給大家看一道例題: 題目:填空練習(指向指標的指標) 1.程式分析:      2.程式原始碼: main() { char *s[]={"man","woman","girl","boy","sister"}; char **q; int k; for(

利用css3的animation實現點點點loading動畫效果(

設置 str ack rdp 提交 ssi frame spin color box-shadow實現的打點效果 簡介 box-shadow理論上可以生成任意的圖形效果,當然也就可以實現點點點的loading效果了。 實現原理 html代碼,首先需要寫如下html代

靜態布局自適應布局流式布局響應式布局彈性布局

彈性 href 窗口 遮擋 正常 阮一峰 布局 變化 發生   近期學習,有很多感想,有時候看似相近的概念,其實意義卻不相同。所以學習要針對不同的名詞有明確的區分意識。   抽空時間,打算學習下display:flex;本以為就是一個小小的知識點,正式去研究的時候,才發現d

Java多線程——Synchronized(同步鎖)Lock以及線程池

ati auto bsp lock eas 根據 引入 封裝 util Java多線程 Java中,可運行的程序都是有一個或多個進程組成。進程則是由多個線程組成的。最簡單的一個進程,會包括mian線程以及GC線程。 線程的狀態 線程狀態由以下一張網上圖片來說明: