Android 源码分析三 View 绘制
前文讲完 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 neededint 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 contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(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 internallyfinal ArrayList 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 viewrenderNode = 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 backgroundsif ((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 tryreturn 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 创建出了 DisplayListCanvasfinal 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 defaultif (!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_DRAWmPrivateFlags |= 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 pathonDescendantInvalidated(child, child);return;}ViewParent parent = this;if (attachInfo != null) {...do {...// 依次返回 parent 的 parent 最后到 ViewRootImplparent = parent.invalidateChildInParent(location, dirty);if (view != null) {// Account for transform on current parentMatrix 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 parentmPrivateFlags |= (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 为 falseview.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 bitint 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 positioninvalidate(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 overmGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() {@Overridepublic 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_STARTEDonAnimationStart();}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 requestsparent.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 propertyif (mAnimatorMap.size() > 0) {Animator animatorToCancel = null;Set 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 itanimatorToCancel = 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@Overridepublic void onAnimationUpdate(ValueAnimator animation) {PropertyBundle propertyBundle = mAnimatorMap.get(animation);if (propertyBundle == null) {// Shouldn't happen, but just to play it safereturn;}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 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() 方法,默认返回为 falsealphaHandled = 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 不会触发 invalidateif (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_DRAWNmPrivateFlags |= 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_VALIDif ((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 和 对应的 bitmapattachInfo.mCanvas = null;} else {// This case should hopefully never or seldom happencanvas = 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 disappearingChildren = mDisappearingChildren;...if (animation != null && !animation.getFillAfter()) {view.clearAnimation();}...
}
复制代码 最后吐槽一波 View 绘制相关的 flag ,又多又复杂,程序员的小巧思,用着用着 flag 一多,感觉这就成灾难了。
转载于:https://juejin.im/post/5d01125a5188255ee806a2da
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
