Android 源码分析三 View 绘制

栏目: 编程工具 · 发布时间: 5年前

内容简介:前文讲完

前文讲完 View 的测量过程,接着讲 View 的绘制。对于 View 绘制,首先想到就是 Canvas 对象以及 draw() onDraw() 相关回调方法。 接下来,也带着一些问题来分析源码:

  1. Canvas 是啥时候由谁创建?
  2. parent 的 Canvas 和 child 的 Canvas 是同一个对象吗?
  3. 每次 draw() onDraw() 方法中的 Canvas 是同一个对象吗?
  4. 动画效果的实现原理

draw() 方法

View 的绘制,就是从自己的 draw() 方法开始,我们先从 draw() 方法中看看能找出一些什么线索(measure() layout() draw() 三个方法中,只有draw() 方法能被复写,据说这是一个 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() 方法已经有很详细的注释,详尽的流程分为七个步骤,如果没有渐变遮罩那些效果,通常效果就是绘制背景色(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 中的实现效果呢?

// 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 绘制顺序。

接着看看调用的 View.draw() 三个参数的重载方法,好戏开始啦。

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 。

到这里,虽然还没看到 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 中的资源。

Android 源码分析三 View 绘制

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 的相关属性,结束后不会还原。

Android 源码分析三 View 绘制

####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 一多,感觉这就成灾难了。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Flask Web开发实战

Flask Web开发实战

李辉 / 机械工业出版社 / 2018-8-1 / 129

这是一本面向Python程序员的,全面介绍Python Web框架Flask的书。关于本书的详细介绍、相关资源等更多信息可以访问本书的官方主页http://helloflask.com/book了解。 • 国内首本Flask著作,在内容上涵盖完整的Flask Web开发学习路径,在实践上包含完整的Flask Web程序开发流程。同时兼容Python2 .7和Python3.6。 • 内......一起来看看 《Flask Web开发实战》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试