Android 源碼分析三 View 繪製

前文講完 View 的測量過程,接着講 View 的繪製。對於 View 繪製,首先想到就是 Canvas 對象以及 draw() onDraw() 相關回調方法。 接下來,也帶着一些問題來分析源碼:node

  1. Canvas 是啥時候由誰建立?
  2. parent 的 Canvas 和 child 的 Canvas 是同一個對象嗎?
  3. 每次 draw() onDraw() 方法中的 Canvas 是同一個對象嗎?
  4. 動畫效果的實現原理

draw() 方法

View 的繪製,就是從本身的 draw() 方法開始,咱們先從 draw() 方法中看看能找出一些什麼線索(measure() layout() draw() 三個方法中,只有draw() 方法能被複寫,聽說這是一個 bug )。android

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)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // 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) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

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

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

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }
    ...
    if (debugDraw()) {
        debugDrawFocus(canvas);
    }
}
複製代碼

draw() 方法已經有很詳細的註釋,詳盡的流程分爲七個步驟,若是沒有漸變遮罩那些效果,一般效果就是繪製背景色(drawBackGround)繪製內容(onDraw()),分發繪製(dispatchDraw()),繪製前景色,繪製高亮,最後,你看到還有一個 debugDraw() 的判斷,這個是啥呢?程序員

//View
final private void debugDrawFocus(Canvas canvas) {
    if (isFocused()) {
        final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
        final int l = mScrollX;
        final int r = l + mRight - mLeft;
        final int t = mScrollY;
        final int b = t + mBottom - mTop;

        final Paint paint = getDebugPaint();
        paint.setColor(DEBUG_CORNERS_COLOR);

        // Draw squares in corners.
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(l, t, l + cornerSquareSize, t + cornerSquareSize, paint);
        canvas.drawRect(r - cornerSquareSize, t, r, t + cornerSquareSize, paint);
        canvas.drawRect(l, b - cornerSquareSize, l + cornerSquareSize, b, paint);
        canvas.drawRect(r - cornerSquareSize, b - cornerSquareSize, r, b, paint);

        // Draw big X across the view.
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawLine(l, t, r, b, paint);
        canvas.drawLine(l, b, r, t, paint);
    }
}
複製代碼

其實就是開啓 「顯示佈局邊界」以後的那些效果,到這裏,意外發現了一個有趣的東西,開啓 「顯示佈局邊界」的效果原來就是在 Viewdraw() 方法中指定的。接着重點看看 dispatchDraw() 方法實現。在 View 中,這個方法默認是空實現,由於它就是最終 View,沒有 child 須要分發下去。那 ViewGroup 中的實現效果呢?canvas

// ViewGroup dispatchDraw()
 @Override
protected void dispatchDraw(Canvas canvas) {
    ...
    // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    final ArrayList<View> preorderedList = usingRenderNodeProperties
            ? null : buildOrderedChildList();
    final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
        // 獲取 childIndex 
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ...
}
複製代碼

ViewGroup.dispatchDraw() 方法中,最核心邏輯就是遍歷全部的 child , 而後調用 drawChild() 方法,固然,調用 drawChild() 也有一些條件,好比說 View 是可見的。再說 drawChild() 方法以前,咱們能夠先看到,這裏有一個方法來獲取 childIndex ,既然有一個方法,就說明,childIndex 或者說 child 的繪製 index 是能夠改變的咯?緩存

private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
    final int childIndex;
    if (customOrder) {
        if (childIndex1 >= childrenCount) {
            throw new IndexOutOfBoundsException("getChildDrawingOrder() "
                    + "returned invalid index " + childIndex1
                    + " (child count is " + childrenCount + ")");
        }
        childIndex = childIndex1;
    } else {
        childIndex = i;
    }
    return childIndex;
}
複製代碼

getAndVerifyPreorderedIndex() 接收三個參數,第一個是 totalCount,第二個在 parent 中的位置,第三個 customOrder 是說是否支持自定義順序,默認是 false ,能夠經過 setChildrenDrawingOrderEnabled() 方法更改。若是咱們支持自定義繪製順序以後,具體繪製順序就會根據 getChildDrawingOrder() 方法返回,可能你會想了,爲何須要修改繪製順序呢?有必要嗎?那妥妥是有必要的,繪製順序決定了顯示層級。好了,這又算一個額外發現,關於修改 View 繪製順序。app

接着看看調用的 View.draw() 三個參數的重載方法,好戲開始啦。dom

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;
    final boolean childHasIdentityMatrix = hasIdentityMatrix();
    final int parentFlags = parent.mGroupFlags;

    ...

    if (hardwareAcceleratedCanvas) {
        // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
        // retain the flag's value temporarily in the mRecreateDisplayList flag
        // INVALIDATED 這個 flag 被重置,可是它的值被保存到 mRecreateDisplayList 中,後面繪製時須要使用
        mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
        mPrivateFlags &= ~PFLAG_INVALIDATED;
    }

    RenderNode renderNode = null;
    ...
    if (drawingWithRenderNode) {
        // Delay getting the display list until animation-driven alpha values are
        // set up and possibly passed on to the view
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.isValid()) {
            // Uncommon, but possible. If a view is removed from the hierarchy during the call
            // to getDisplayList(), the display list will be marked invalid and we should not
            // try to use it again.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    ...
    if (!drawingWithDrawingCache) {
        // 硬件加速模式下 
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
        } else {
            // 普通模式下
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }
        }
    } 
    ...
    // 重置 mRecreateDisplayList 爲 false 返回是否還有更多 這個和動畫繪製有關係
    mRecreateDisplayList = false;
    return more;
}
複製代碼

在這個方法中,首先要注意的是,canvas.isHardwareAccelerated() 第一行代碼這個判斷,Canvas 不就是單純的 Canvas,裏面還有支持硬件加速不支持硬件加速的區分?先看一哈 Canvas 的種族關係。CanvasBaseCanvas 的一個實現類,它 isHardwareAccelerated 方法是返回的 false,那就是說,確定還有其餘子類咯,果真 RecordingCanvas 、而後還有 DisplayListCanvas ,而後 DisplayListCanvas 這個類中 isHardwareAccelerated() 返回的就是 true 。ide

到這裏,雖然還沒看到 Canvas 在哪裏建立出來,可是至少首先明確了 Canvas 是有細分子類,並且支持硬件加速的不是 Canvas 這個類,而是 DisplayListCanvas 。如今硬件加速默認都是支持的,那咱們能夠先驗證一下 Canvas 的類型。隨便定義兩個 View ,而後寫一個佈局打印以下:源碼分析

TestFrameLayout draw canvas:android.view.DisplayListCanvas@6419f23
TestFrameLayout dispatchDraw canvas:android.view.DisplayListCanvas@6419f23
TestView draw canvas:android.view.DisplayListCanvas@7f9c20
TestView onDraw canvas:android.view.DisplayListCanvas@7f9c20
TestView dispatchDraw dispatchDraw:android.view.DisplayListCanvas@7f9c20
TestView draw canvas:android.view.DisplayListCanvas@369cf9b
TestView onDraw canvas:android.view.DisplayListCanvas@369cf9b
TestView dispatchDraw dispatchDraw:android.view.DisplayListCanvas@369cf9b
複製代碼

首先,Canvas 的確是 DisplayListCanvas 的類型。而後,兩次 draw() 方法,是兩個不一樣的 Canvas 對象。最後,parent 和 child 用的不是同一個對象,彷佛以前提的問題基本上都在這個 log 中所有給出答案。答案知道沒啥用,咱們是看源碼分析具體怎麼操做的。因此,仍是繼續看下去。佈局

接下來,咱們就先看支持硬件加速這條分支,這也是咱們的常規路線。

View 之 flag

在上面的方法中,若是支持硬件加速後,就有這一步驟。這裏涉及到 View中 Flag 操做。View 中有超級多狀態,若是每個都用一個變量來記錄,那就是一個災難。那麼怎麼能用最小的花銷記錄最多的狀態呢?這個就和前文講到 測量模式和測量大小用一個字段的高位和低位就搞定同樣。二進制這個時候就很是高效啦,看看 View 中定義了哪些基礎 flag 。

// for mPrivateFlags:
    static final int PFLAG_WANTS_FOCUS                 = 0x00000001;
    static final int PFLAG_FOCUSED                     = 0x00000002;
    static final int PFLAG_SELECTED                    = 0x00000004;
    static final int PFLAG_IS_ROOT_NAMESPACE           = 0x00000008;
    static final int PFLAG_HAS_BOUNDS                  = 0x00000010;
    static final int PFLAG_DRAWN                       = 0x00000020;
    static final int PFLAG_DRAW_ANIMATION              = 0x00000040;
    static final int PFLAG_SKIP_DRAW                   = 0x00000080;
    static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200;
    static final int PFLAG_DRAWABLE_STATE_DIRTY        = 0x00000400;
    static final int PFLAG_MEASURED_DIMENSION_SET      = 0x00000800;
    static final int PFLAG_FORCE_LAYOUT                = 0x00001000;
    static final int PFLAG_LAYOUT_REQUIRED             = 0x00002000;
    private static final int PFLAG_PRESSED             = 0x00004000;
    static final int PFLAG_DRAWING_CACHE_VALID         = 0x00008000;
複製代碼

定義是定義好了,那麼怎麼修改狀態呢?這裏就用到位運算 與 或 非 異或 等操做符號。

// 判斷是否包含 一個 flag (同 1 爲 1 else 0)
view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0 
// 清除一個 flag  
view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT
// 設置一個 flag  (遇 1 爲 1 else 0)
view.mPrivateFlags |= View.PFLAG_FORCE_LAYOUT

//檢查是否改變
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
// changed ==1 爲 true 
int changed = mViewFlags ^ old;


// View.setFlag()
if ((changed & DRAW_MASK) != 0) {
    if ((mViewFlags & WILL_NOT_DRAW) != 0) {
        if (mBackground != null
                || mDefaultFocusHighlight != null
                || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
            mPrivateFlags &= ~PFLAG_SKIP_DRAW;
        } else {
            mPrivateFlags |= PFLAG_SKIP_DRAW;
        }
    } else {
        mPrivateFlags &= ~PFLAG_SKIP_DRAW;
    }
}
複製代碼

接着在上面方法中,就開始對 flag 進行操做。

if (hardwareAcceleratedCanvas) {
        // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
        // retain the flag's value temporarily in the mRecreateDisplayList flag
        // INVALIDATED 這個 flag 被重置,可是它的值被保存到 mRecreateDisplayList 中,後面繪製時須要使用
        mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
        mPrivateFlags &= ~PFLAG_INVALIDATED;
    }
複製代碼

首先將 mRecreateDisplayList 賦值爲是否包含 PFLAG_INVALIDATED 的狀態。而後重置 PFLAG_INVALIDATED flag。緊接着就調用 updateDisplayListIfDirty() 方法,接下來重點看下 updateDisplayListIfDirty() 方法中的邏輯。

// View
@NonNull
public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) {
        // can't populate RenderNode, don't try
        return renderNode;
    }
    // 1.沒有 PFLAG_DRAWING_CACHE_VALID 或者 renderNode 不可用 或者 mRecreateDisplayList 爲 true (含有 PFLAG_INVALIDATED )
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {
        // Don't need to recreate the display list, just need to tell our
        // children to restore/recreate theirs
        // 2.這裏 mRecreateDisplayList 在 前面的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 中肯定
        // 設置過 PFLAG_INVALIDATED 纔會返回 true 須要從新建立 canvas 並繪製 
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            // 異常狀況二
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();

            return renderNode; // no work needed
        }

        // If we got here, we're recreating it. Mark it as such to ensure that
        // we copy in child display lists into ours in drawChild()
        mRecreateDisplayList = true;

        int width = mRight - mLeft;
        int height = mBottom - mTop;
        int layerType = getLayerType();
        // 3.這裏,經過 renderNode 建立出了 DisplayListCanvas
        final DisplayListCanvas canvas = renderNode.start(width, height);
        canvas.setHighContrastText(mAttachInfo.mHighContrastText);

        try {
            if (layerType == LAYER_TYPE_SOFTWARE) {
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {
                computeScroll();

                canvas.translate(-mScrollX, -mScrollY);
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                // Fast path for layouts with no backgrounds
                // 4.ViewGroup 不用繪製內容
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    dispatchDraw(canvas);
                    drawAutofilledHighlight(canvas);
                    if (mOverlay != null && !mOverlay.isEmpty()) {
                        mOverlay.getOverlayView().draw(canvas);
                    }
                    if (debugDraw()) {
                        debugDrawFocus(canvas);
                    }
                } else {
                    draw(canvas);
                }
            }
        } finally {
            // 5.一些收尾工做
            renderNode.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        // 異常狀況一 相關標誌添加和清除
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}
複製代碼

updateDisplayIfDirty() 方法中,這些標誌必定要注意:

mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
複製代碼

再繪製前,這三個 flag 的改變是必定要執行的,具體說就是 PFLAG_DRAWN PFLAG_DRAWING_CACHE_VALID 被添加,PFLAG_DIRTY_MASK 被清除。這裏就再添加一個疑問,PFLAG_DRAWN PFLAG_DRAWING_CACHE_VALID 何時被移除的?PFLAG_DIRTY_MASK 何時被添加的?這個放後面說。先直接來分析一波代碼執行。

接着看相關源碼,在註釋1的地方,三個條件。

(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)
複製代碼

已目前的狀況,咱們就知道第三個字段是上一個方法清除 PFLAG_INVALIDATED 時保存的它的狀態。咱們姑且認爲它就是 true ,那接着註釋2中的添加就不知足,接着就到 註釋3中,這裏很清楚能夠看到,Canvas 在這裏被建立出來啦。第一個問題終於找到答案,CanvasView 本身在 updateDiasplayIfDirty() 方法中建立出來的。建立 Canvas 以後,若是是硬解模式下,就到註釋4中,這裏是一個判斷,若是有 PFLAG_SKIP_DRAW 這個 flag,直接就調用 dispatchDraw() 分發下去,不然就調用本身的 draw() 方法回到文章開始說的 draw() 方法中。

那麼這個 PFLAG_SKIP_DRAW 又是哪裏會有設置呢?在 ViewGroup 的構造方法中,我看到了這個:

private void initViewGroup() {
    // ViewGroup doesn't draw by default
    if (!debugDraw()) {
        setFlags(WILL_NOT_DRAW, DRAW_MASK);
    }
   ...
}
複製代碼

若是沒有開啓「顯示邊界佈局」,直接會添加 WILL_NOT_DRAW 的 flag。這裏就是一個對於 ViewGroup 的優化,由於 ViewGroup 繪製 content (調用 onDraw())方法有時候是多餘的,它的內容明顯是由 child 本身完成。可是,若是我給 ViewGroup 設置了背景,文章開頭 draw() 方法分析中就有說,先繪製背景色,那若是這個時候跳過 ViewGroupdraw() 直接調用 dispatchDraw() 方法確定有問題,或者說在設置背景色相關方法中,View 又會修改這個 flag。

public void setBackgroundDrawable(Drawable background) {
    ...

    if (background != null) {
        ...
        applyBackgroundTint();

        // Set callback last, since the view may still be initializing.
        background.setCallback(this);
        // 清除 PFLAG_SKIP_DRAW 
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
            mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            requestLayout = true;
        }
    } else {
        /* Remove the background */
        mBackground = null;
        if ((mViewFlags & WILL_NOT_DRAW) != 0
                && (mDefaultFocusHighlight == null)
                && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
            // 若是沒有背景,就再次添加 PFLAG_SKIP_DRAW
            mPrivateFlags |= PFLAG_SKIP_DRAW;
        }
        requestLayout = true;
    }

    computeOpaqueFlags();

    if (requestLayout) {
        // 請求從新佈局
        requestLayout();
    }

    mBackgroundSizeChanged = true;
    // 要求從新繪製
    invalidate(true);
    invalidateOutline();
}
複製代碼

注意,更新背景以後會觸發 requestLayout()invalidate() 兩個方法。

那若是三個條件都不知足(異常狀況一),就是直接更改 flag 結束了;還有就是註釋2中的一種狀況(異常狀況二),mRecreateDisplayList 爲 false,不會再去建立 Canvas ,也就是說它不須要從新繪製本身,可是會調用 dispatchGetDisplayList() 方法。這個方法在 View 中是空實現,在 ViewGroup 中會遍歷 child 調用 recreateChildDisplayList(child) 方法。

private void recreateChildDisplayList(View child) {
    child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
    child.mPrivateFlags &= ~PFLAG_INVALIDATED;
    child.updateDisplayListIfDirty();
    child.mRecreateDisplayList = false;
}
複製代碼

這個方法像極了 View.draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法中的核心設置。做用就是在不繪製本身的狀況下,將繪製再次進行分發。這兩種狀況何時觸發?第一種不太好猜,第二種其實很好理解,那就是當咱們調用 invalidate() 調用以後,確定就只更新對應的 View,不可能說所有都去從新繪製,這樣太浪費資源和作無用功。具體的下面作分析。

Canvas 建立和複用

在上面 updateDisplayListIfDirty() 方法中,咱們解決了第一個問題,Canvas 是在這個方法中建立:

// 3.這裏,經過 renderNode 建立出了 DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);
複製代碼

接下來看看 Canvas 具體建立過程。首先是 renderNode 這個對象。在 View 的構造方法中,

mRenderNode = RenderNode.create(getClass().getName(), this);
複製代碼

內部就是調用 native 相關方法,傳入對應 class 名稱和所屬對象。接着再看看 renderNode 建立 Canvas 的過程。

//DisplayListCanvas
static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
    if (node == null) throw new IllegalArgumentException("node cannot be null");
    // acquire  取出最後一個
    DisplayListCanvas canvas = sPool.acquire();
    if (canvas == null) {
        canvas = new DisplayListCanvas(node, width, height);
    } else {
        nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
                width, height);
    }
    canvas.mNode = node;
    canvas.mWidth = width;
    canvas.mHeight = height;
    return canvas;
}
複製代碼

Canvas 的管理用到 pool 的概念,經過一個池來實現回收(release)複用(acquire) ,具體怎麼回收複用的,下面有貼對應源碼。最後在 finally 中,會對 Canvas 進行釋放。 這裏 pool 並無初始 size,或者說初始 size 就是 0 ,最大 size 是在 DisplayListCanvas 中指定爲 POOL_LIMIT = 25DisplayListCanvas 還額外指定了 drawBitmap() 方法中 bitmap 最大的 size 100M。

//RenderNode 
public void end(DisplayListCanvas canvas) {
    long displayList = canvas.finishRecording();
    nSetDisplayList(mNativeRenderNode, displayList);
    canvas.recycle();
}

//DisplayListCanvas
void recycle() {
    mNode = null;
    // 存入最後一個
    sPool.release(this);
}

//SimplePool
@Override
@SuppressWarnings("unchecked")
public T acquire() {
    if (mPoolSize > 0) {
        final int lastPooledIndex = mPoolSize - 1;
        T instance = (T) mPool[lastPooledIndex];
        mPool[lastPooledIndex] = null;
        mPoolSize--;
        return instance;
    }
    return null;
}
//SimplePool
@Override
public boolean release(@NonNull T instance) {
    if (isInPool(instance)) {
        throw new IllegalStateException("Already in the pool!");
    }
    if (mPoolSize < mPool.length) {
        mPool[mPoolSize] = instance;
        mPoolSize++;
        return true;
    }
    return false;
}
複製代碼

這裏也具體說明上文提出的問題,每一次繪製,View 都會使用一個新的 Canvas(從pool中取出來),不排除是以前已經使用過的。使用完畢,回收又放回 pool。ViewGroupchild 之間不會同時使用同一個 Canvas ,可是能共享一個 pool 中的資源。

View draw.png

invalidate()

好了,上面捋清楚 View 繪製的整個過程後,提出的問題也解決的差很少了,可是還遺留了 updateListPlayIfDirty() 方法中兩個異常狀況。若是三個條件都不知足(異常狀況一),就直接更改 flag 結束;還有就是註釋2中的一種狀況(異常狀況二),mRecreateDisplayList 爲false,不會再去建立 Canvas ,也就是說它不須要從新繪製本身。

接着,把這兩個異常狀況解決就圓滿結束。要解決上面兩個異常問題,咱們就必須來分析一波主動調用 invalidate() 請求繪製。

在調用 invalidate() 方法以後,最後會調用到 invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) 方法,並且 invalidateCache fullInvalidate 都爲 true

//View
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    ...

    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)) {
        // fullInvalidate 時 clear PFLAG_DRAWN 
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }
        // 調用 draw 等的通行證,
        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;
        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();
            }
        }
    }
}
複製代碼

invalidateInternal() 方法中,一開始提到的那些 flag 又出現了。其中 PFLAG_DRAWNPFLAG_DRAWING_CACHE_VALID 被清除掉 PFLAG_DIRTYPFLAG_INVALIDATED 被添加。這裏這些 flag 請注意,這是解答那兩個異常狀況的核心。接着會調用到 invalidateChild() 方法。

//ViewGroup
@Deprecated
@Override
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 {
            ...
            // 依次返回 parent 的 parent 最後到 ViewRootImpl
            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                // Account for transform on current parent
                Matrix m = view.getMatrix();
                if (!m.isIdentity()) {
                    RectF boundingRect = attachInfo.mTmpTransformRect;
                    boundingRect.set(dirty);
                    m.mapRect(boundingRect);
                    dirty.set((int) Math.floor(boundingRect.left),
                            (int) Math.floor(boundingRect.top),
                            (int) Math.ceil(boundingRect.right),
                            (int) Math.ceil(boundingRect.bottom));
                }
            }
        } while (parent != null);
    }
}

這個方法中,若是是硬解支持,直接走 `onDescendantInvalidated(child, child)` 方法。接着看看這個方法的具體實現。

@Override
@CallSuper
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    /*
     * HW-only, Rect-ignoring damage codepath
     *
     * We don't deal with rectangles here, since RenderThread native code computes damage for
     * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
     */

    // if set, combine the animation flag into the parent
    mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

    // target 須要被從新繪製時,至少有 invalidate flag 
    if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
        // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
        // optimization in provides in a DisplayList world.
        // 先清除全部 DIRTY 相關 flag 而後 加上 DIRTY flag 
        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

        // simplified invalidateChildInParent behavior: clear cache validity to be safe...
        // 清除 PFLAG_DRAWING_CACHE_VALID 標誌
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }

    // ... and mark inval if in software layer that needs to repaint (hw handled in native)
    if (mLayerType == LAYER_TYPE_SOFTWARE) {
        // Layered parents should be invalidated. Escalate to a full invalidate (and note that
        // we do this after consuming any relevant flags from the originating descendant)
        mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
        target = this;
    }

    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

這個方法中,會依次向上讓 parent 調用 `onDescendantInvalidated()` ,而在這個方法中,會爲 parent 添加 `PFLAG_DIRTY` 和 重置 `PFLAG_DRAWING_CACHE_VALID` 標誌,可是,可是,請注意這裏沒有給 parent 設置過 `PFLAG_INVALIDATED` ,由於除了發起 `invalidate()` 的 targetView ,其餘 `View` 理論上不用從新繪製。

ViewTree 的盡頭是啥呢?是 `ViewRootImpl` ,這裏就不詳細展開說了,在那裏,最後會調用 `performTraversals()` 方法,在該方法中,最後會調用 `performDraw()` 方法,在這個方法中,最後又會調用 `mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this)`,而該方法最後會調用到 `updateViewTreeDisplayList()` 方法。

//ViewRootImpl
//ThreadedRenderer
private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    // 調用 invalidate 到這裏時,除了 targetView 其餘 View 都未設置過 PFLAG_INVALIDATED mRecreateDisplayList 爲 false
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}
複製代碼

這個方法和上面介紹過的 ViewGroup.recreateChildDisplayList(View child) 很類似,就是多了 PFLAG_DRAWN 設置。到這裏,就開始整個 View 繪製的分發啦。調用上文提到的 updateDisplayListIfDirty() 方法。 再來看這個異常狀況一:

(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList))
複製代碼

調用 invalidate() 後在 onDescendantInvalidated() 中, PFLAG_DRAWING_CACHE_VALID 都被清除掉了。因此不會走到異常狀況一中。接着,看異常狀況二,mRecreateDisplayList 爲 false ,這個就符合了,在 mRecreateDisplayList() 方法向上傳遞過程當中,並無給 targetView 之外的 View 設置過 PFLAG_INVALIDATED ,因此異常狀況二就是咱們調用 invalidate() 主動要求繪製時會執行。

那異常狀況一到底怎麼觸發呢?經過上面分析能夠知道,每一次繪製結束,PFLAG_DRAWING_CACHE_VALID 都會被添加。每一次開始繪製,PFLAG_DRAWING_CACHE_VALID 又會被清除。當一個 View 知足沒有設置 PFLAG_INVALIDATED 而且 PFLAG_DRAWING_CACHE_VALID 又沒有被清除(至少說沒有觸發 invalidate())。

public void requestLayout() {
    ...

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}
複製代碼

當一個 View 調用 requestLayout() 以後,PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED 都會被添加。可是,通常來講,requestLayout() 不會觸發 draw() 方法的,奧妙就在這裏。當 requestLayout() 調用到 ViewRootImpl 中以後,又一次執行 performTraversals() 時,完成測量等邏輯以後,再到上文提到的 updateViewTreeDisplayList() 方法時,PFLAG_INVALIDATED 並無被設置,所以 mRecreateDisplayList 爲 false,此時只有 targetView 纔有設置 PFLAG_INVALIDATED 。而後 PFLAG_DRAWING_CACHE_VALID 默認就被設置,並無被清除。因此,在 RootView.updateDisplayListIfDirty() 執行時,RootView 直接就走到了異常狀況一。這也是 requestLayout() 不會回調 draw() 方法的緣由。

可是 requestLayout() 不觸發 draw() 不是絕對的。若是你的 size 發生改變,在 layout() 方法中,最後會調用 setFrame() 方法,在該方法中,若是 size change,它會本身調用 invalidate(sizeChange)

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        invalidate(sizeChanged);
        ...

        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}
複製代碼

動畫相關

這裏咱們看看 AnimationAnimator 的區別,效果上說就是 Animation 不會改變一個 View 的真實值,動畫結束後又還原(固然,你能夠設置 fillAfter 爲 true ,可是它的佈局仍是在初始位置,只是更改了繪製出來的效果)。 Animator 會直接改變一個 View 的相關屬性,結束後不會還原。

View invalidate.png

Animation

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    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());
        }
    }
    // 動畫完成 
    if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
            mLayoutAnimationController.isDone() && !more) {
        // We want to erase the drawing cache and notify the listener after the
        // next frame is drawn because one extra invalidate() is caused by
        // drawChild() after the animation is over
        mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
        final Runnable end = new Runnable() {
           @Override
           public void run() {
               notifyAnimationListener();
           }
        };
        post(end);
    }

}

//View 
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...
        if (a != null && !more) {
        if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {
            onSetAlpha(255);
        }
        //完成相關回調重置動畫
        parent.finishAnimatingView(this, a);
    }
    ...
}


//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);
        // 本身增長 PFLAG_ANIMATION_STARTED
        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;
        a.getTransformation(drawingTime, invalidationTransform, 1f);
    } else {
        invalidationTransform = t;
    }
    // 動畫沒有結束
    if (more) {
        // 不會改變界限
        if (!a.willChangeBounds()) {
            if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                    ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
                // 設置了 layoutAnimation 會到這裏
                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.invalidate() 從新繪製
                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;
}
複製代碼

dispatchDraw() 中最後調用 applyLegacyAnimation() 方法,在這方法中,若是是首次初始化,會增長 PFLAG_ANIMATION_STARTED 標誌,接着根據 getTransformation() 返回動畫是否沒有結束。若是沒有結束,就添加相關 flag ,使用 parent.invalidate(mLeft, mTop, mRight, mBottom) 完成對特定區域繪製的更新。

Animator

對於 Animator ,最簡單的寫法就是:

view.animate()
    .scaleX(0.5f)
    .scaleY(0.5f)
    .start()

private void animatePropertyBy(int constantName, float startValue, float byValue) {
    // First, cancel any existing animations on this property
    if (mAnimatorMap.size() > 0) {
        Animator animatorToCancel = null;
        Set<Animator> animatorSet = mAnimatorMap.keySet();
        for (Animator runningAnim : animatorSet) {
            PropertyBundle bundle = mAnimatorMap.get(runningAnim);
            if (bundle.cancel(constantName)) {
                // property was canceled - cancel the animation if it's now empty
                // Note that it's safe to break out here because every new animation
                // on a property will cancel a previous animation on that property, so
                // there can only ever be one such animation running.
                if (bundle.mPropertyMask == NONE) {
                    // the animation is no longer changing anything - cancel it
                    animatorToCancel = runningAnim;
                    break;
                }
            }
        }
        if (animatorToCancel != null) {
            animatorToCancel.cancel();
        }
    }

    NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
    mPendingAnimations.add(nameValuePair);
    mView.removeCallbacks(mAnimationStarter);
    mView.postOnAnimation(mAnimationStarter);
}
複製代碼

animatePropertyBy() 內部註釋很清楚,每個屬性的動畫效果只有一個有效,最新的會將上一個取消掉,在該方法最後,你會看到它直接有開始執行動畫效果,等等,咱們這裏還咩有調用 start() 呢? 這意思就是說咱們若是須要馬上執行,壓根兒不用手動調用 start() 方法?答案就是這樣的,咱們徹底不用手動調用 start() 去確認開啓動畫。

private void startAnimation() {
    if (mRTBackend != null && mRTBackend.startAnimation(this)) {
        return;
    }
    ...
    animator.addUpdateListener(mAnimatorEventListener);
    animator.addListener(mAnimatorEventListener);
    ...
    animator.start();
}
複製代碼

這裏須要注意一下這個 mAnimatorEventListener ,它實現了 Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener 兩個接口。在 onAnimationUpdate() 方法中:

// AnimatorEventListener
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        PropertyBundle propertyBundle = mAnimatorMap.get(animation);
        if (propertyBundle == null) {
            // Shouldn't happen, but just to play it safe
            return;
        }

        boolean hardwareAccelerated = mView.isHardwareAccelerated();

        // alpha requires slightly different treatment than the other (transform) properties.
        // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
        // logic is dependent on how the view handles an internal call to onSetAlpha().
        // We track what kinds of properties are set, and how alpha is handled when it is
        // set, and perform the invalidation steps appropriately.
        boolean alphaHandled = false;
        //若是不支持硬件加速,那麼將從新出發 draw() 方法
        if (!hardwareAccelerated) {
            mView.invalidateParentCaches();
        }
        float fraction = animation.getAnimatedFraction();
        int propertyMask = propertyBundle.mPropertyMask;
        if ((propertyMask & TRANSFORM_MASK) != 0) {
            mView.invalidateViewProperty(hardwareAccelerated, false);
        }
        ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
        if (valueList != null) {
            int count = valueList.size();
            for (int i = 0; i < count; ++i) {
                NameValuesHolder values = valueList.get(i);
                float value = values.mFromValue + fraction * values.mDeltaValue;
                // alpha 的 設置被區分開
                if (values.mNameConstant == ALPHA) {
                    // 最終調用 view.onSetAlpha() 方法,默認返回爲 false
                    alphaHandled = mView.setAlphaNoInvalidation(value);
                } else {
                    // 屬性動畫修改屬性的核心方法
                    setValue(values.mNameConstant, value);
                }
            }
        }
        if ((propertyMask & TRANSFORM_MASK) != 0) {
            if (!hardwareAccelerated) {
                // 不支持硬件加速,手動添加 PFLAG_DRAWN 標誌
                mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
            }
        }
        // invalidate(false) in all cases except if alphaHandled gets set to true
        // via the call to setAlphaNoInvalidation(), above
        // 一般都是 false 不會觸發 invalidate
        if (alphaHandled) {
            mView.invalidate(true);
        } else {
            // alphaHandled false 的話 不管 硬解仍是軟解都會調用該方法
            mView.invalidateViewProperty(false, false);
        }
        if (mUpdateListener != null) {
            mUpdateListener.onAnimationUpdate(animation);
        }
    }

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

// View alpha 單獨設置
boolean setAlphaNoInvalidation(float alpha) {
    ensureTransformationInfo();
    if (mTransformationInfo.mAlpha != alpha) {
        mTransformationInfo.mAlpha = alpha;
        boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255));
        if (subclassHandlesAlpha) {
            mPrivateFlags |= PFLAG_ALPHA_SET;
            return true;
        } else {
            mPrivateFlags &= ~PFLAG_ALPHA_SET;
            mRenderNode.setAlpha(getFinalAlpha());
        }
    }
    return false;
}
複製代碼

能夠看到,在 animator 內部設置的 AnimatorEventListener 對象中,回調 onAnimationUpdate() 方法核心是經過 setValue(values.mNameConstant, value) 方法改變相關屬性。

private void setValue(int propertyConstant, float value) {
    final View.TransformationInfo info = mView.mTransformationInfo;
    final RenderNode renderNode = mView.mRenderNode;
    switch (propertyConstant) {
        case TRANSLATION_X:
            renderNode.setTranslationX(value);
            break;
        ...
        case Y:
            renderNode.setTranslationY(value - mView.mTop);
            break;
        ...
        case ALPHA:
            info.mAlpha = value;
            renderNode.setAlpha(value);
            break;
    }
}
複製代碼

能夠看到,屬性動畫的本質是直接修改 renderNode 的相關屬性,包括 alpha ,雖然 alpha 並無沒有直接調用 setValue() 的方法更改,但本質都是調用到 renderNode 的相關方法。可是,在 Animator 實際執行過程當中,又是區分了 軟解和硬解兩種狀況。

若是是硬解的話,直接修改 renderNode 相關屬性,DisplayListCanvas 是關聯了 renderNode ,雖然都調用了 invalidateViewProperty() 。 若是是軟解的話,首先調用 mView.invalidateParentCaches() 爲 parent 添加 PFLAG_INVALIDATED 標誌,若是存在 transform ,就爲本身再添加 PFLAG_DRAWN。 接着在 mView.invalidateViewProperty(false, false) 中,開始和硬解有了區別。

// View.invalidateViewProperty()
void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
    // 軟解 直接 走 invalidate(false) 方法
    if (!isHardwareAccelerated()
            || !mRenderNode.isValid()
            || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        if (invalidateParent) {
            invalidateParentCaches();
        }
        if (forceRedraw) {
            // 強制刷新 也是添加 PFLAG_DRAWN
            mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
        }
        invalidate(false);
    } else {
        // 硬解 走 damageInParent() 方法
        damageInParent();
    }
}
複製代碼

在硬解中,直接調用 damageInParent() ,由於這個時候,PFLAG_INVALIDATED 並無設置。在最後的 updateDisplayListIfDirty() 方法中,不會觸發 draw() 或者 dispatchDraw() ,流程結束。

而後軟解,走 invalidate(false) 使用 false 的話,PFLAG_INVALIDATED 不會被添加,PFLAG_DRAWING_CACHE_VALID 不會被清除, 最後調用 ViewGroup.invalidateChild() 方法,這個方法以前只分析過 硬解 的狀況。

@Override
public final void invalidateChild(View child, final Rect dirty) {
    ...
        // 軟解
        do {
            ...
            parent = parent.invalidateChildInParent(location, dirty);
            ...
        } while (parent != null);
    }
}

@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        // either DRAWN, or DRAWING_CACHE_VALID
        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
                != FLAG_OPTIMIZE_INVALIDATE) {
           ...
        } else {

           ...
            location[CHILD_LEFT_INDEX] = mLeft;
            location[CHILD_TOP_INDEX] = mTop;

            mPrivateFlags &= ~PFLAG_DRAWN;
        }
        // 這裏將 PFLAG_DRAWING_CACHE_VALID 標誌清除,這個很重要
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        if (mLayerType != LAYER_TYPE_NONE) {
            mPrivateFlags |= PFLAG_INVALIDATED;
        }

        return mParent;
    }

    return null;
}
複製代碼

經過上面連個方法,最終會調用到 ViewRootImpl 中開始從新分發,過程和上面分析一致,須要注意的是在 invalidateChildInParent() 方法中 PFLAG_DRAWING_CACHE_VALID 被清除,PFLAG_INVALIDATED 被添加。因此在最後調用 updateDisplayListIfDirty() 方法中不會走到上面提到的兩種異常狀況中。

@NonNull
public RenderNode updateDisplayListIfDirty() {
    ...
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {
        try {
            // 使用軟解最終調用到這裏
            if (layerType == LAYER_TYPE_SOFTWARE) {
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {
                ...
            }
        } finally {
            renderNode.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}
複製代碼

能夠看到,使用軟解,並不會按以前的硬解分析的走到 dispatchDraw() 或者 draw() 方法,而是調用 buildDrawingCache(boolean autoScale) 方法,在該方法中,最後又會調用 buildDrawingCacheImpl(autoScale) 方法。

private void buildDrawingCacheImpl(boolean autoScale) {
    ...

    Canvas canvas;
    if (attachInfo != null) {
        //從 attachInfo 總獲取 Canvas ,沒有就建立並存入 attachInfo 中
        canvas = attachInfo.mCanvas;
        if (canvas == null) {
            canvas = new Canvas();
        }
        canvas.setBitmap(bitmap);
        // Temporarily clobber the cached Canvas in case one of our children
        // is also using a drawing cache. Without this, the children would
        // steal the canvas by attaching their own bitmap to it and bad, bad
        // thing would happen (invisible views, corrupted drawings, etc.)
        // 這裏有置空操做,防止其餘  子 View 同時也想使用當前的 Canvas 和 對應的 bitmap
        attachInfo.mCanvas = null;
    } else {
        // This case should hopefully never or seldom happen
        canvas = new Canvas(bitmap);
    }

    ...

    mPrivateFlags |= PFLAG_DRAWN;
    if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
            mLayerType != LAYER_TYPE_NONE) {
        mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
    }

    // Fast path for layouts with no backgrounds
    // 這裏開始就和硬解同樣的邏輯,看是否須要直接調用 dispatchDraw() 
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
    } else {
        draw(canvas);
    }

    canvas.restoreToCount(restoreCount);
    canvas.setBitmap(null);

    if (attachInfo != null) {
        // Restore the cached Canvas for our siblings
        // 對應以前的置空,這裏完成恢復
        attachInfo.mCanvas = canvas;
    }
}
複製代碼

buildDrawingCacheImpl() 能夠看到軟解時 Canvas 的緩存是經過 attachInfo 來實現,也就是說,軟解時,建立一次 Canvas 以後,以後每次繪製 都是使用的同一個 Canvas 對象,這個和硬解是有卻別的。

到這裏,View 動畫效果介紹完畢,Animation 會增長 PFLAG_DRAW_ANIMATION 標誌並調用 invalidate() 從新繪製。而對於 Animator 來講,硬解的話,不會調用到 invalidate() 去從新繪製,而是直接更改 renderNode 的相關屬性。軟解的話,也須要重走 invalidate() 方法。最後再說下 AnimationfillAfter 屬性,若是設置了話,View 也會保持動畫的最終效果,那這個是怎麼實現的呢? 其實就是根據是否要清除動畫信息來實現的。這個方法會在 draw() 三個參數的方法中被調用。

void finishAnimatingView(final View view, Animation animation) {
    final ArrayList<View> disappearingChildren = mDisappearingChildren;
    ...
    if (animation != null && !animation.getFillAfter()) {
        view.clearAnimation();
    }
    ...
}
複製代碼

最後吐槽一波 View 繪製相關的 flag ,又多又複雜,程序員的小巧思,用着用着 flag 一多,感受這就成災難了。

相關文章
相關標籤/搜索