前文講完 View
的測量過程,接着講 View
的繪製。對於 View
繪製,首先想到就是 Canvas
對象以及 draw()
onDraw()
相關回調方法。 接下來,也帶着一些問題來分析源碼:node
Canvas
是啥時候由誰建立?Canvas
和 child 的 Canvas
是同一個對象嗎?draw()
onDraw()
方法中的 Canvas
是同一個對象嗎?View
的繪製,就是從本身的 draw()
方法開始,咱們先從 draw()
方法中看看能找出一些什麼線索(measure() layout() draw() 三個方法中,只有draw() 方法能被複寫,聽說這是一個 bug )。android
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
複製代碼
draw()
方法已經有很詳細的註釋,詳盡的流程分爲七個步驟,若是沒有漸變遮罩那些效果,一般效果就是繪製背景色(drawBackGround)繪製內容(onDraw()),分發繪製(dispatchDraw()),繪製前景色,繪製高亮,最後,你看到還有一個 debugDraw()
的判斷,這個是啥呢?程序員
//View
final private void debugDrawFocus(Canvas canvas) {
if (isFocused()) {
final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
final int l = mScrollX;
final int r = l + mRight - mLeft;
final int t = mScrollY;
final int b = t + mBottom - mTop;
final Paint paint = getDebugPaint();
paint.setColor(DEBUG_CORNERS_COLOR);
// Draw squares in corners.
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(l, t, l + cornerSquareSize, t + cornerSquareSize, paint);
canvas.drawRect(r - cornerSquareSize, t, r, t + cornerSquareSize, paint);
canvas.drawRect(l, b - cornerSquareSize, l + cornerSquareSize, b, paint);
canvas.drawRect(r - cornerSquareSize, b - cornerSquareSize, r, b, paint);
// Draw big X across the view.
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(l, t, r, b, paint);
canvas.drawLine(l, b, r, t, paint);
}
}
複製代碼
其實就是開啓 「顯示佈局邊界」以後的那些效果,到這裏,意外發現了一個有趣的東西,開啓 「顯示佈局邊界」的效果原來就是在 View
的 draw()
方法中指定的。接着重點看看 dispatchDraw()
方法實現。在 View
中,這個方法默認是空實現,由於它就是最終 View
,沒有 child 須要分發下去。那 ViewGroup
中的實現效果呢?canvas
// ViewGroup dispatchDraw()
@Override
protected void dispatchDraw(Canvas canvas) {
...
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
// 獲取 childIndex
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
複製代碼
ViewGroup.dispatchDraw()
方法中,最核心邏輯就是遍歷全部的 child
, 而後調用 drawChild()
方法,固然,調用 drawChild()
也有一些條件,好比說 View
是可見的。再說 drawChild()
方法以前,咱們能夠先看到,這裏有一個方法來獲取 childIndex
,既然有一個方法,就說明,childIndex
或者說 child 的繪製 index 是能夠改變的咯?緩存
private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
final int childIndex;
if (customOrder) {
if (childIndex1 >= childrenCount) {
throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ "returned invalid index " + childIndex1
+ " (child count is " + childrenCount + ")");
}
childIndex = childIndex1;
} else {
childIndex = i;
}
return childIndex;
}
複製代碼
getAndVerifyPreorderedIndex()
接收三個參數,第一個是 totalCount
,第二個在 parent
中的位置,第三個 customOrder
是說是否支持自定義順序,默認是 false ,能夠經過 setChildrenDrawingOrderEnabled()
方法更改。若是咱們支持自定義繪製順序以後,具體繪製順序就會根據 getChildDrawingOrder()
方法返回,可能你會想了,爲何須要修改繪製順序呢?有必要嗎?那妥妥是有必要的,繪製順序決定了顯示層級。好了,這又算一個額外發現,關於修改 View 繪製順序。app
接着看看調用的 View.draw()
三個參數的重載方法,好戲開始啦。dom
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
*
* If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
* HW accelerated, it can't handle drawing RenderNodes.
*/
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
final int parentFlags = parent.mGroupFlags;
...
if (hardwareAcceleratedCanvas) {
// Clear INVALIDATED flag to allow invalidation to occur during rendering, but
// retain the flag's value temporarily in the mRecreateDisplayList flag
// INVALIDATED 這個 flag 被重置,可是它的值被保存到 mRecreateDisplayList 中,後面繪製時須要使用
mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
mPrivateFlags &= ~PFLAG_INVALIDATED;
}
RenderNode renderNode = null;
...
if (drawingWithRenderNode) {
// Delay getting the display list until animation-driven alpha values are
// set up and possibly passed on to the view
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
// Uncommon, but possible. If a view is removed from the hierarchy during the call
// to getDisplayList(), the display list will be marked invalid and we should not
// try to use it again.
renderNode = null;
drawingWithRenderNode = false;
}
}
...
if (!drawingWithDrawingCache) {
// 硬件加速模式下
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// 普通模式下
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
}
...
// 重置 mRecreateDisplayList 爲 false 返回是否還有更多 這個和動畫繪製有關係
mRecreateDisplayList = false;
return more;
}
複製代碼
在這個方法中,首先要注意的是,canvas.isHardwareAccelerated()
第一行代碼這個判斷,Canvas
不就是單純的 Canvas
,裏面還有支持硬件加速不支持硬件加速的區分?先看一哈 Canvas
的種族關係。Canvas
是 BaseCanvas
的一個實現類,它 isHardwareAccelerated
方法是返回的 false,那就是說,確定還有其餘子類咯,果真 RecordingCanvas
、而後還有 DisplayListCanvas
,而後 DisplayListCanvas
這個類中 isHardwareAccelerated()
返回的就是 true 。ide
到這裏,雖然還沒看到 Canvas
在哪裏建立出來,可是至少首先明確了 Canvas
是有細分子類,並且支持硬件加速的不是 Canvas
這個類,而是 DisplayListCanvas
。如今硬件加速默認都是支持的,那咱們能夠先驗證一下 Canvas
的類型。隨便定義兩個 View ,而後寫一個佈局打印以下:源碼分析
TestFrameLayout draw canvas:android.view.DisplayListCanvas@6419f23
TestFrameLayout dispatchDraw canvas:android.view.DisplayListCanvas@6419f23
TestView draw canvas:android.view.DisplayListCanvas@7f9c20
TestView onDraw canvas:android.view.DisplayListCanvas@7f9c20
TestView dispatchDraw dispatchDraw:android.view.DisplayListCanvas@7f9c20
TestView draw canvas:android.view.DisplayListCanvas@369cf9b
TestView onDraw canvas:android.view.DisplayListCanvas@369cf9b
TestView dispatchDraw dispatchDraw:android.view.DisplayListCanvas@369cf9b
複製代碼
首先,Canvas
的確是 DisplayListCanvas
的類型。而後,兩次 draw()
方法,是兩個不一樣的 Canvas
對象。最後,parent 和 child 用的不是同一個對象,彷佛以前提的問題基本上都在這個 log 中所有給出答案。答案知道沒啥用,咱們是看源碼分析具體怎麼操做的。因此,仍是繼續看下去。佈局
接下來,咱們就先看支持硬件加速這條分支,這也是咱們的常規路線。
在上面的方法中,若是支持硬件加速後,就有這一步驟。這裏涉及到 View
中 Flag 操做。View
中有超級多狀態,若是每個都用一個變量來記錄,那就是一個災難。那麼怎麼能用最小的花銷記錄最多的狀態呢?這個就和前文講到 測量模式和測量大小用一個字段的高位和低位就搞定同樣。二進制這個時候就很是高效啦,看看 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
,不可能說所有都去從新繪製,這樣太浪費資源和作無用功。具體的下面作分析。
在上面 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 中的資源。
好了,上面捋清楚 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
的相關屬性,結束後不會還原。
@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
,最簡單的寫法就是:
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 一多,感受這就成災難了。