内容简介:前文讲完
前文讲完 View
的测量过程,接着讲 View
的绘制。对于 View
绘制,首先想到就是 Canvas
对象以及 draw()
onDraw()
相关回调方法。 接下来,也带着一些问题来分析源码:
-
Canvas
是啥时候由谁创建? - parent 的
Canvas
和 child 的Canvas
是同一个对象吗? - 每次
draw()
onDraw()
方法中的Canvas
是同一个对象吗? - 动画效果的实现原理
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); } } 复制代码
其实就是开启 「显示布局边界」之后的那些效果,到这里,意外发现了一个有趣的东西,开启 「显示布局边界」的效果原来就是在 View
的 draw()
方法中指定的。接着重点看看 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
的种族关系。 Canvas
是 BaseCanvas
的一个实现类,它 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
在这里被创建出来啦。第一个问题终于找到答案, Canvas
是 View
自己在 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()
方法分析中就有说,先绘制背景色,那如果这个时候跳过 ViewGroup
的 draw()
直接调用 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 = 25
, DisplayListCanvas
还额外指定了 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。 ViewGroup
和 child
之间不会同时使用同一个 Canvas
,但是能共享一个 pool 中的资源。
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_DRAWN
和 PFLAG_DRAWING_CACHE_VALID
被清除掉 PFLAG_DIRTY
和 PFLAG_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_LAYOUT
和 PFLAG_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; } 复制代码
动画相关
这里我们看看 Animation
和 Animator
的区别,效果上说就是 Animation
不会改变一个 View
的真实值,动画结束后又还原(当然,你可以设置 fillAfter 为 true ,但是它的布局还是在初始位置,只是更改了绘制出来的效果)。 Animator
会直接改变一个 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()
方法。最后再说下 Animation
的 fillAfter
属性,如果设置了话, View
也会保持动画的最终效果,那这个是怎么实现的呢? 其实就是根据是否要清除动画信息来实现的。这个方法会在 draw() 三个参数的方法中被调用。
void finishAnimatingView(final View view, Animation animation) { final ArrayList<View> disappearingChildren = mDisappearingChildren; ... if (animation != null && !animation.getFillAfter()) { view.clearAnimation(); } ... } 复制代码
最后吐槽一波 View
绘制相关的 flag ,又多又复杂,程序员的小巧思,用着用着 flag 一多,感觉这就成灾难了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- View绘制流程源码分析
- 基于源码分析 Android View 绘制机制
- Android源码分析之View绘制流程
- YYText 源码剖析:CoreText 与异步绘制
- RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
- Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
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开发实战》 这本书的介绍吧!