Android 源碼分析三 View 繪制

前文講完 View 的測量過程,接著講 View 的繪制。對于 View 繪制,首先想到就是 Canvas 對象以及 draw() onDraw() 相關(guān)回調(diào)方法。
接下來,也帶著一些問題來分析源碼:

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

draw() 方法

View 的繪制,就是從自己的 draw() 方法開始,我們先從 draw() 方法中看看能找出一些什么線索(measure() layout() draw() 三個方法中,只有draw() 方法能被復寫,據(jù)說這是一個 bug )。

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() 方法已經(jīng)有很詳細的注釋,詳盡的流程分為七個步驟,如果沒有漸變遮罩那些效果,通常效果就是繪制背景色(drawBackGround)繪制內(nèi)容(onDraw()),分發(fā)繪制(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);
    }
}

其實就是開啟 「顯示布局邊界」之后的那些效果,到這里,意外發(fā)現(xiàn)了一個有趣的東西,開啟 「顯示布局邊界」的效果原來就是在 Viewdraw() 方法中指定的。接著重點看看 dispatchDraw() 方法實現(xiàn)。在 View 中,這個方法默認是空實現(xiàn),因為它就是最終 View,沒有 child 需要分發(fā)下去。那 ViewGroup 中的實現(xiàn)效果呢?

// 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 , 然后調(diào)用 drawChild() 方法,當然,調(diào)用 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() 接收三個參數(shù),第一個是 totalCount,第二個在 parent 中的位置,第三個 customOrder 是說是否支持自定義順序,默認是 false ,可以通過 setChildrenDrawingOrderEnabled() 方法更改。如果我們支持自定義繪制順序之后,具體繪制順序就會根據(jù) getChildDrawingOrder() 方法返回,可能你會想了,為什么需要修改繪制順序呢?有必要嗎?那妥妥是有必要的,繪制順序決定了顯示層級。好了,這又算一個額外發(fā)現(xiàn),關(guān)于修改 View 繪制順序。

接著看看調(diào)用的 View.draw() 三個參數(shù)的重載方法,好戲開始啦。

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 返回是否還有更多 這個和動畫繪制有關(guān)系
    mRecreateDisplayList = false;
    return more;
}

在這個方法中,首先要注意的是,canvas.isHardwareAccelerated() 第一行代碼這個判斷,Canvas 不就是單純的 Canvas,里面還有支持硬件加速不支持硬件加速的區(qū)分?先看一哈 Canvas 的種族關(guān)系。CanvasBaseCanvas 的一個實現(xiàn)類,它 isHardwareAccelerated 方法是返回的 false,那就是說,肯定還有其他子類咯,果然 RecordingCanvas 、然后還有 DisplayListCanvas ,然后 DisplayListCanvas 這個類中 isHardwareAccelerated() 返回的就是 true 。

到這里,雖然還沒看到 Canvas 在哪里創(chuàng)建出來,但是至少首先明確了 Canvas 是有細分子類,而且支持硬件加速的不是 Canvas 這個類,而是 DisplayListCanvas ?,F(xiàn)在硬件加速默認都是支持的,那我們可以先驗證一下 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 中全部給出答案。答案知道沒啥用,我們是看源碼分析具體怎么操作的。所以,還是繼續(xù)看下去。

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

View 之 flag

在上面的方法中,如果支持硬件加速后,就有這一步驟。這里涉及到 View中 Flag 操作。View 中有超級多狀態(tài),如果每一個都用一個變量來記錄,那就是一個災(zāi)難。那么怎么能用最小的花銷記錄最多的狀態(tài)呢?這個就和前文講到 測量模式和測量大小用一個字段的高位和低位就搞定一樣。二進制這個時候就非常高效啦,看看 View 中定義了哪些基礎(chǔ) 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;

定義是定義好了,那么怎么修改狀態(tài)呢?這里就用到位運算 與 或 非 異或 等操作符號。

// 判斷是否包含 一個 flag (同 1 為 1 else 0)
view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0 
// 清除一個 flag  
view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT
// 設(shè)置一個 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 的狀態(tài)。然后重置 PFLAG_INVALIDATED flag。緊接著就調(diào)用 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) 中確定
        // 設(shè)置過 PFLAG_INVALIDATED 才會返回 true 需要重新創(chuàng)建 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 創(chuàng)建出了 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 不用繪制內(nèi)容
                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 {
        // 異常情況一 相關(guān)標志添加和清除
        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 的改變是一定要執(zhí)行的,具體說就是 PFLAG_DRAWN PFLAG_DRAWING_CACHE_VALID 被添加,PFLAG_DIRTY_MASK 被清除。這里就再添加一個疑問,PFLAG_DRAWN PFLAG_DRAWING_CACHE_VALID 什么時候被移除的?PFLAG_DIRTY_MASK 什么時候被添加的?這個放后面說。先直接來分析一波代碼執(zhí)行。

接著看相關(guān)源碼,在注釋1的地方,三個條件。

    (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)

已目前的情況,我們就知道第三個字段是上一個方法清除 PFLAG_INVALIDATED 時保存的它的狀態(tài)。我們姑且認為它就是 true ,那接著注釋2中的添加就不滿足,接著就到 注釋3中,這里很清楚可以看到,Canvas 在這里被創(chuàng)建出來啦。第一個問題終于找到答案,CanvasView 自己在 updateDiasplayIfDirty() 方法中創(chuàng)建出來的。創(chuàng)建 Canvas 之后,如果是硬解模式下,就到注釋4中,這里是一個判斷,如果有 PFLAG_SKIP_DRAW 這個 flag,直接就調(diào)用 dispatchDraw() 分發(fā)下去,否則就調(diào)用自己的 draw() 方法回到文章開始說的 draw() 方法中。

那么這個 PFLAG_SKIP_DRAW 又是哪里會有設(shè)置呢?在 ViewGroup 的構(gòu)造方法中,我看到了這個:

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

如果沒有開啟「顯示邊界布局」,直接會添加 WILL_NOT_DRAW 的 flag。這里就是一個對于 ViewGroup 的優(yōu)化,因為 ViewGroup 繪制 content (調(diào)用 onDraw())方法有時候是多余的,它的內(nèi)容明顯是由 child 自己完成。但是,如果我給 ViewGroup 設(shè)置了背景,文章開頭 draw() 方法分析中就有說,先繪制背景色,那如果這個時候跳過 ViewGroupdraw() 直接調(diào)用 dispatchDraw() 方法肯定有問題,或者說在設(shè)置背景色相關(guān)方法中,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();
}

注意,更新背景之后會觸發(fā) requestLayout()invalidate() 兩個方法。

那如果三個條件都不滿足(異常情況一),就是直接更改 flag 結(jié)束了;還有就是注釋2中的一種情況(異常情況二),mRecreateDisplayList 為 false,不會再去創(chuàng)建 Canvas ,也就是說它不需要重新繪制自己,但是會調(diào)用 dispatchGetDisplayList() 方法。這個方法在 View 中是空實現(xiàn),在 ViewGroup 中會遍歷 child 調(diào)用 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) 方法中的核心設(shè)置。作用就是在不繪制自己的情況下,將繪制再次進行分發(fā)。這兩種情況什么時候觸發(fā)?第一種不太好猜,第二種其實很好理解,那就是當我們調(diào)用 invalidate() 調(diào)用之后,肯定就只更新對應(yīng)的 View,不可能說全部都去重新繪制,這樣太浪費資源和做無用功。具體的下面做分析。

Canvas 創(chuàng)建和復用

在上面 updateDisplayListIfDirty() 方法中,我們解決了第一個問題,Canvas 是在這個方法中創(chuàng)建:

// 3.這里,通過 renderNode 創(chuàng)建出了 DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);

接下來看看 Canvas 具體創(chuàng)建過程。首先是 renderNode 這個對象。在 View 的構(gòu)造方法中,

mRenderNode = RenderNode.create(getClass().getName(), this);

內(nèi)部就是調(diào)用 native 相關(guān)方法,傳入對應(yīng) class 名稱和所屬對象。接著再看看 renderNode 創(chuàng)建 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 的概念,通過一個池來實現(xiàn)回收(release)復用(acquire) ,具體怎么回收復用的,下面有貼對應(yīng)源碼。最后在 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中取出來),不排除是之前已經(jīng)使用過的。使用完畢,回收又放回 pool。ViewGroupchild 之間不會同時使用同一個 Canvas ,但是能共享一個 pool 中的資源。

View draw.png

invalidate()

好了,上面捋清楚 View 繪制的整個過程后,提出的問題也解決的差不多了,但是還遺留了 updateListPlayIfDirty() 方法中兩個異常情況。如果三個條件都不滿足(異常情況一),就直接更改 flag 結(jié)束;還有就是注釋2中的一種情況(異常情況二),mRecreateDisplayList 為false,不會再去創(chuàng)建 Canvas ,也就是說它不需要重新繪制自己。

接著,把這兩個異常情況解決就圓滿結(jié)束。要解決上面兩個異常問題,我們就必須來分析一波主動調(diào)用 invalidate() 請求繪制。

在調(diào)用 invalidate() 方法之后,最后會調(diào)用到 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;
        }
        // 調(diào)用 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 又出現(xiàn)了。其中 PFLAG_DRAWNPFLAG_DRAWING_CACHE_VALID 被清除掉 PFLAG_DIRTYPFLAG_INVALIDATED 被添加。這里這些 flag 請注意,這是解答那兩個異常情況的核心。接著會調(diào)用到 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) 方法。接著看看這個方法的具體實現(xiàn)。

@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 相關(guān) 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 調(diào)用 onDescendantInvalidated() ,而在這個方法中,會為 parent 添加 PFLAG_DIRTY 和 重置 PFLAG_DRAWING_CACHE_VALID 標志,但是,但是,請注意這里沒有給 parent 設(shè)置過 PFLAG_INVALIDATED ,因為除了發(fā)起 invalidate() 的 targetView ,其他 View 理論上不用重新繪制。

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

//ViewRootImpl
//ThreadedRenderer
private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    // 調(diào)用 invalidate 到這里時,除了 targetView 其他 View 都未設(shè)置過 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 設(shè)置。到這里,就開始整個 View 繪制的分發(fā)啦。調(diào)用上文提到的 updateDisplayListIfDirty() 方法。 再來看這個異常情況一:

(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList))

調(diào)用 invalidate() 后在 onDescendantInvalidated() 中, PFLAG_DRAWING_CACHE_VALID 都被清除掉了。所以不會走到異常情況一中。接著,看異常情況二,mRecreateDisplayList 為 false ,這個就符合了,在 mRecreateDisplayList() 方法向上傳遞過程中,并沒有給 targetView 以外的 View 設(shè)置過 PFLAG_INVALIDATED ,所以異常情況二就是我們調(diào)用 invalidate() 主動要求繪制時會執(zhí)行。

那異常情況一到底怎么觸發(fā)呢?通過上面分析可以知道,每一次繪制結(jié)束,PFLAG_DRAWING_CACHE_VALID 都會被添加。每一次開始繪制,PFLAG_DRAWING_CACHE_VALID 又會被清除。當一個 View 滿足沒有設(shè)置 PFLAG_INVALIDATED 并且 PFLAG_DRAWING_CACHE_VALID 又沒有被清除(至少說沒有觸發(fā) invalidate())。

public void requestLayout() {
    ...

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}

當一個 View 調(diào)用 requestLayout() 之后,PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED 都會被添加。但是,一般來說,requestLayout() 不會觸發(fā) draw() 方法的,奧妙就在這里。當 requestLayout() 調(diào)用到 ViewRootImpl 中之后,又一次執(zhí)行 performTraversals() 時,完成測量等邏輯之后,再到上文提到的 updateViewTreeDisplayList() 方法時,PFLAG_INVALIDATED 并沒有被設(shè)置,因此 mRecreateDisplayList 為 false,此時只有 targetView 才有設(shè)置 PFLAG_INVALIDATED 。然后 PFLAG_DRAWING_CACHE_VALID 默認就被設(shè)置,并沒有被清除。所以,在 RootView.updateDisplayListIfDirty() 執(zhí)行時,RootView 直接就走到了異常情況一。這也是 requestLayout() 不會回調(diào) draw() 方法的原因。

但是 requestLayout() 不觸發(fā) draw() 不是絕對的。如果你的 size 發(fā)生改變,在 layout() 方法中,最后會調(diào)用 setFrame() 方法,在該方法中,如果 size change,它會自己調(diào)用 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;
}

動畫相關(guān)

這里我們看看 AnimationAnimator 的區(qū)別,效果上說就是 Animation 不會改變一個 View 的真實值,動畫結(jié)束后又還原(當然,你可以設(shè)置 fillAfter 為 true ,但是它的布局還是在初始位置,只是更改了繪制出來的效果)。 Animator 會直接改變一個 View 的相關(guān)屬性,結(jié)束后不會還原。

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);
        }
        //完成相關(guān)回調(diào)重置動畫
        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;
    }
    // 動畫沒有結(jié)束
    if (more) {
        // 不會改變界限
        if (!a.willChangeBounds()) {
            if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                    ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
                // 設(shè)置了 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
                //一般情況到這里,調(diào)用 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() 中最后調(diào)用 applyLegacyAnimation() 方法,在這方法中,如果是首次初始化,會增加 PFLAG_ANIMATION_STARTED 標志,接著根據(jù) getTransformation() 返回動畫是否沒有結(jié)束。如果沒有結(jié)束,就添加相關(guān) flag ,使用 parent.invalidate(mLeft, mTop, mRight, mBottom) 完成對特定區(qū)域繪制的更新。

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() 內(nèi)部注釋很清楚,每一個屬性的動畫效果只有一個有效,最新的會將上一個取消掉,在該方法最后,你會看到它直接有開始執(zhí)行動畫效果,等等,我們這里還咩有調(diào)用 start() 呢? 這意思就是說我們?nèi)绻枰⒖虉?zhí)行,壓根兒不用手動調(diào)用 start() 方法?答案就是這樣的,我們完全不用手動調(diào)用 start() 去確認開啟動畫。

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

這里需要注意一下這個 mAnimatorEventListener ,它實現(xiàn)了 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;
        //如果不支持硬件加速,那么將重新出發(fā) 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 的 設(shè)置被區(qū)分開
                if (values.mNameConstant == ALPHA) {
                    // 最終調(diào)用 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 不會觸發(fā) invalidate
        if (alphaHandled) {
            mView.invalidate(true);
        } else {
            // alphaHandled false 的話 無論 硬解還是軟解都會調(diào)用該方法
            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 單獨設(shè)置
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 內(nèi)部設(shè)置的 AnimatorEventListener 對象中,回調(diào) onAnimationUpdate() 方法核心是通過 setValue(values.mNameConstant, value) 方法改變相關(guān)屬性。

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

可以看到,屬性動畫的本質(zhì)是直接修改 renderNode 的相關(guān)屬性,包括 alpha ,雖然 alpha 并沒有沒有直接調(diào)用 setValue() 的方法更改,但本質(zhì)都是調(diào)用到 renderNode 的相關(guān)方法。但是,在 Animator 實際執(zhí)行過程中,又是區(qū)分了 軟解和硬解兩種情況。

如果是硬解的話,直接修改 renderNode 相關(guān)屬性,DisplayListCanvas 是關(guān)聯(lián)了 renderNode ,雖然都調(diào)用了 invalidateViewProperty() 。
如果是軟解的話,首先調(diào)用 mView.invalidateParentCaches() 為 parent 添加 PFLAG_INVALIDATED 標志,如果存在 transform ,就為自己再添加 PFLAG_DRAWN。 接著在 mView.invalidateViewProperty(false, false) 中,開始和硬解有了區(qū)別。

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

在硬解中,直接調(diào)用 damageInParent() ,因為這個時候,PFLAG_INVALIDATED 并沒有設(shè)置。在最后的 updateDisplayListIfDirty() 方法中,不會觸發(fā) draw() 或者 dispatchDraw() ,流程結(jié)束。

然后軟解,走 invalidate(false) 使用 false 的話,PFLAG_INVALIDATED 不會被添加,PFLAG_DRAWING_CACHE_VALID 不會被清除, 最后調(diào)用 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;
}

通過上面連個方法,最終會調(diào)用到 ViewRootImpl 中開始重新分發(fā),過程和上面分析一致,需要注意的是在 invalidateChildInParent() 方法中 PFLAG_DRAWING_CACHE_VALID 被清除,PFLAG_INVALIDATED 被添加。所以在最后調(diào)用 updateDisplayListIfDirty() 方法中不會走到上面提到的兩種異常情況中。

@NonNull
public RenderNode updateDisplayListIfDirty() {
    ...
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {
        try {
            // 使用軟解最終調(diào)用到這里
            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() 方法,而是調(diào)用 buildDrawingCache(boolean autoScale) 方法,在該方法中,最后又會調(diào)用 buildDrawingCacheImpl(autoScale) 方法。

private void buildDrawingCacheImpl(boolean autoScale) {
    ...

    Canvas canvas;
    if (attachInfo != null) {
        //從 attachInfo 總獲取 Canvas ,沒有就創(chuàng)建并存入 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 和 對應(yīng)的 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
    // 這里開始就和硬解一樣的邏輯,看是否需要直接調(diào)用 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
        // 對應(yīng)之前的置空,這里完成恢復
        attachInfo.mCanvas = canvas;
    }
}

buildDrawingCacheImpl() 可以看到軟解時 Canvas 的緩存是通過 attachInfo 來實現(xiàn),也就是說,軟解時,創(chuàng)建一次 Canvas 之后,之后每次繪制 都是使用的同一個 Canvas 對象,這個和硬解是有卻別的。

到這里,View 動畫效果介紹完畢,Animation 會增加 PFLAG_DRAW_ANIMATION 標志并調(diào)用 invalidate() 重新繪制。而對于 Animator 來說,硬解的話,不會調(diào)用到 invalidate() 去重新繪制,而是直接更改 renderNode 的相關(guān)屬性。軟解的話,也需要重走 invalidate() 方法。最后再說下 AnimationfillAfter 屬性,如果設(shè)置了話,View 也會保持動畫的最終效果,那這個是怎么實現(xiàn)的呢? 其實就是根據(jù)是否要清除動畫信息來實現(xiàn)的。這個方法會在 draw() 三個參數(shù)的方法中被調(diào)用。

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

最后吐槽一波 View 繪制相關(guān)的 flag ,又多又復雜,程序員的小巧思,用著用著 flag 一多,感覺這就成災(zāi)難了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容