View 的繪制系列文章:
-
Android View 繪制流程之 DecorView 與 ViewRootImpl
-
Android View 的繪制流程之 Measure 過程詳解 (一)
-
Android View 的繪制流程之 Layout 和 Draw 過程詳解 (二)
-
Android View 的事件分發原理解析
在上一篇 Android View 的繪制流程之 Measure 過程詳解 (一),已經詳細的分析了 DecorView 和其子 View 的測量過程,接下去就要開始講 layout 和 draw 流程。下面開始進入分析:
DecorView Layout 階段
在 ViewRootImpl 中,調用 performLayout 方法來確定 DecorView 在屏幕中的位置,下面看下具體的代碼邏輯:
// ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try {
// 根據測量結果進行繪制 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters != null) { // Set this flag to indicate that any further requests are happening during // the second pass, which may result in posting those requests to the next // frame instead mHandlingLayoutInLayoutRequest = true; // Process fresh layout requests, then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = false; // Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters != null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { int numValidRequests = finalRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = finalRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); } } }); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
當在 layout 繪制過程中,收到了關於重新 layout 的請求,會先判斷這些請求里面哪些是有效的,如果是有效的,那么就必須先處理,清除 layout-request flags (View.PFLAG_FORCE_LAYOUT)這些標記,再做一次徹底的重繪工作:重新測量,layout。
那么對於 DecorView 來說,調用 layout 方法,就是對它自身進行布局,注意到傳遞的參數分別是 0,0, host.getMeasuredWidth, host.getMeasuredHeigh,它們分別代表了一個 View 的上下左右四個位置,顯然,DecorView 的左上位置為 0,然后寬高為它的測量寬高,下面來看 layout 的具體代碼:
// ViewGroup public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { if (mTransition != null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; } }
由於 ViewGroup 的 layout 方法是 final 類型,子類不能重寫,這里調用了父類的 View#layout 方法,下面看看該方法是如何操作的:
// View
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; // 設置界面的顯示大小 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 發生了改變,或者是需要重新 layout,那么就會進入 onLayout 邏輯 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 開始 onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } // 清除請求 layout 的標記 mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } final boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (!wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; if (canTakeFocus()) { // We have a robust focus, so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) { // This is a weird case. Most-likely the user, rather than ViewRootImpl, called // layout. In this case, there's no guarantee that parent layouts will be evaluated // and thus the safest action is to clear focus here. clearFocusInternal(null, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (!hasParentWantsFocus()) { // original requestFocus was likely on this view directly, so just clear focus clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } // otherwise, we let parents handle re-assigning focus during their layout passes. } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused != null) { // Try to restore focus as close as possible to our starting focus. if (!restoreDefaultFocus() && !hasParentWantsFocus()) { // Give up and clear focus once we've reached the top-most parent which wants // focus. focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); } }
調用了 setFrame 方法,並把四個位置信息傳遞進去,這個方法用於確定 View 的四個頂點的位置,看下具體的代碼:
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } // 有一個不一樣說明發生了改變 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); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); // 做好標記 mPrivateFlags |= PFLAG_HAS_BOUNDS; // size 改變的回調 if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child invalidateParentCaches(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; }
賦值后,前后對比大小,如果發生了改變,就會調用了 sizeChange()方法,最終會回調 onSizeChanged,標明 view 的尺寸發生了變化,第一次 laout 的時候也會調用。在自定義view 的時候,可以在這里獲取控件的寬和高度。
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) { onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); ...... }
子 View Layout 流程
當 DecorView 確定好了自己的位置之后,開始調用 onLayout 來確定子 view 的位置。對於 onLayout 方法,View 和 ViewGroup 類是空實現,接下來看 FrameLayout 的實現:
// FrameLayout protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); }
可以看到,該方法調用了 layoutChildren 來確定子 view 的位置。
// FrameLaout
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); // 獲取 DecoverView 剩余的空間范圍 final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop;
// DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START 默認是在左上角
int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; // 確定左邊起始點 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } // 確定上邊起始點 switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
先梳理一下以上邏輯:
-
首先先獲取父容器的 padding 值,得到 DecorView 的可用於顯示的空間范圍。
-
然后遍歷其每一個子 View,根據子 View 的 layout_gravity 屬性、子 View 的測量寬高、父容器的 padding 值、來確定子 View 的左上角的坐標位置
-
然后調用 child.layout 方法,參數是左上角坐標和自身寬高結合起來的,這樣就可以確定子 View 的位置。
最終調用 layout 是 View#layout,前面已經分析過,就不在分析了。
可以看到子 View 的布局流程也很簡單,如果子 View 是一個 ViewGroup,那么就會重復以上步驟,如果是一個 View,那么會直接調用 View#layout 方法,根據以上分析,在該方法內部會設置 view 的四個布局參數,接着調用 onLayout 方法 :
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
此方法是一個空方法,也就是說需要子類去實現此方法,不同的 View 實現方式不同,這里就不分析了。
layout階段的基本思想也是由根View開始,遞歸地完成整個控件樹的布局(layout)工作。
對於寬高的獲取這里在總結下:
這里需要注意一下,在非一般情況下,也就是通過人為設置,重寫View的layout()強行設置,這種情況下,測量的值與最終的值是不一樣的。
Layout 整體的流程圖
上面分別從 View 和 ViewGroup 的角度講解了布局流程,這里再以流程圖的形式歸納一下整個 Layout 過程,便於加深記憶:
DecorView Draw 流程
Draw 的入口也是在 ViewRootImpl 中,執行 ViewRootImpl#performTraversals 中會執行 ViewRootIml#performDraw:
private void performDraw() { ... //fullRedrawNeeded,它的作用是判斷是否需要重新繪制全部視圖 draw(fullRedrawNeeded); ... }
然后會執行到 ViewRootImpl#draw:
private void draw(boolean fullRedrawNeeded) { ... //獲取mDirty,該值表示需要重繪的區域 final Rect dirty = mDirty; if (mSurfaceHolder != null) { // The app owns the surface, we won't draw. dirty.setEmpty(); if (animating) { if (mScroller != null) { mScroller.abortAnimation(); } disposeResizeBuffer(); } return; } //如果fullRedrawNeeded為真,則把dirty區域置為整個屏幕,表示整個視圖都需要繪制 //第一次繪制流程,需要繪制所有視圖 if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } }
接着會執行到 ViewRootIml#drawSoftware,然后在 ViewRootIml#drawSoftware 會執行到 mView.draw(canvas)。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { final Canvas canvas; //鎖定canvas區域,由dirty區域決定 //這個canvas就是我們想在上面繪制東西的畫布 canvas = mSurface.lockCanvas(dirty); ... //畫布支持位圖的密度,和手機分辨率相關 canvas.setDensity(mDensity); ... if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } ... canvas.translate(-xoff, -yoff); ... //正式開始繪制 mView.draw(canvas); ... //提交需要繪制的東西 surface.unlockCanvasAndPost(canvas); }
mView.draw(canvas) 開始真正的繪制。此處 mView 就是 DecorView,先看 DecorView 中 Draw 的方法:
public void draw(Canvas canvas) { super.draw(canvas); if (mMenuBackground != null) { mMenuBackground.draw(canvas); } }
里面會調用 View#draw:
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); } // 如果可以跳過2和5步 final int viewFlags = mViewFlags; //判斷是否有繪制衰退邊緣的標示 boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; // 如果沒有繪制衰退邊緣只需要3,4,6步 if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(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); // we're done... return; } /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */ boolean drawTop = false; boolean drawBottom = false; boolean drawLeft = false; boolean drawRight = false; float topFadeStrength = 0.0f; float bottomFadeStrength = 0.0f; float leftFadeStrength = 0.0f; float rightFadeStrength = 0.0f; // Step 2, save the canvas' layers int paddingLeft = mPaddingLeft; final boolean offsetRequired = isPaddingOffsetRequired(); if (offsetRequired) { paddingLeft += getLeftPaddingOffset(); } int left = mScrollX + paddingLeft; int right = left + mRight - mLeft - mPaddingRight - paddingLeft; int top = mScrollY + getFadeTop(offsetRequired); int bottom = top + getFadeHeight(offsetRequired); if (offsetRequired) { right += getRightPaddingOffset(); bottom += getBottomPaddingOffset(); } final ScrollabilityCache scrollabilityCache = mScrollCache; final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; // clip the fade length if top and bottom fades overlap // overlapping fades produce odd-looking artifacts if (verticalEdges && (top + length > bottom - length)) { length = (bottom - top) / 2; } // also clip horizontal fades if necessary if (horizontalEdges && (left + length > right - length)) { length = (right - left) / 2; } if (verticalEdges) { topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength())); drawTop = topFadeStrength * fadeHeight > 1.0f; bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength())); drawBottom = bottomFadeStrength * fadeHeight > 1.0f; } if (horizontalEdges) { leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength())); drawLeft = leftFadeStrength * fadeHeight > 1.0f; rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength())); drawRight = rightFadeStrength * fadeHeight > 1.0f; } saveCount = canvas.getSaveCount(); int solidColor = getSolidColor(); if (solidColor == 0) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } } else { scrollabilityCache.setFadeColor(solidColor); } // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers final Paint p = scrollabilityCache.paint; final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { matrix.setScale(1, fadeHeight * bottomFadeStrength); matrix.postRotate(180); matrix.postTranslate(left, bottom); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, bottom - length, right, bottom, p); } if (drawLeft) { matrix.setScale(1, fadeHeight * leftFadeStrength); matrix.postRotate(-90); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, left + length, bottom, p); } if (drawRight) { matrix.setScale(1, fadeHeight * rightFadeStrength); matrix.postRotate(90); matrix.postTranslate(right, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(right - length, top, right, bottom, p); } canvas.restoreToCount(saveCount); // 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); }
可以看到,draw過程比較復雜,但是邏輯十分清晰,而官方注釋也清楚地說明了每一步的做法。我們首先來看一開始的標記位 dirtyOpaque,該標記位的作用是判斷當前 View 是否是透明的,如果 View 是透明的,那么根據下面的邏輯可以看出,將不會執行一些步驟,比如繪制背景、繪制內容等。這樣很容易理解,因為一個 View 既然是透明的,那就沒必要繪制它了。接着是繪制流程的六個步驟,這里先小結這六個步驟分別是什么,然后再展開來講。繪制流程的六個步驟:
-
對 View 的背景進行繪制
-
保存當前的圖層信息(可跳過)
-
繪制 View 的內容
-
對 View 的子 View 進行繪制(如果有子 View )
-
繪制 View 的褪色的邊緣,類似於陰影效果(可跳過)
-
繪制 View 的裝飾(例如:滾動條)
其中第2步和第5步是可以跳過的,我們這里不做分析,我們重點來分析其它步驟。
ViewGroup子類默認情況下就是不執行 onDraw 方法的,在 ViewGroup 源碼中的 initViewGroup() 方法中設置了一個標記,源碼如下:
private void initViewGroup() { // ViewGroup doesn't draw by default if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } ...... }
看第二行注釋也知道,ViewGroup 默認情況下是不會 draw 的。第四行調用 setFlags 方法設置標記 WILL_NOT_DRAW,在回到 View 中 draw 方法看第2行代碼:
1 final int privateFlags = mPrivateFlags; 2 final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 3 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
setFlags 方法就是對 View中mPrivateFlags 值進行相應改變,我們設置標記 WILL_NOT_DRAW 那么 dirtyOpaque 得到的值就為 true,從而 if (!dirtyOpaque) 不成立,也就不會執行onDraw 方法。
1. 繪制背景
View#drawBackground
private void drawBackground(Canvas canvas) { //獲取背景的Drawable,沒有就不需要繪制 final Drawable background = mBackground; if (background == null) { return; } //確定背景Drawable邊界 setBackgroundBounds(); ... //如果有偏移量先偏移畫布再將drawable繪制上去 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); //此處會執行各種Drawable對應的draw方法 background.draw(canvas); //把畫布的原點移回去,drawable在屏幕上的位置不動 canvas.translate(-scrollX, -scrollY); } }
3. 繪制 View 的內容
先跳過第 2 步,是因為不是所有的 View 都需繪制褪色邊緣。DecorView#onDraw:
public void onDraw(Canvas c) { super.onDraw(c); mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent, mStatusColorViewState.view, mNavigationColorViewState.view); }
View#onDraw
protected void onDraw(Canvas canvas) {
}
onDraw 是空實現,需要子 View 自己去繪制。對於DecorView 一般也沒啥內容,除了需要背景顏色等,所以本身並需要繪制啥。
4. 繪制子View
DecorView 繪制完成后,開始繪制子 View,所以 ViewGroup 的繪制需要繪制子 View,直接看看 ViewGroup#dispatchDraw:
protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; //ViewGroup是否有設置子View入場動畫,如果有綁定到View // 啟動動畫控制器 ... //指定修改區域 int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
// 不讓子view繪制在pandding里面,也就是去除padding if (clipToPadding) { clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } ... for (int i = 0; i < childrenCount; i++) { //先取mTransientViews中的View,mTransientViews中的View通過addTransientView添加,它們只是容器渲染的一個item 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; } } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } ... }
ViewGroup#dispatchDraw 的流程是先啟動第一次加到布局中的動畫,然后確定繪制區域,遍歷繪制 View,遍歷 View 的時候優先繪制渲染的 mTransientViews,繪制 View 調用到ViewGroup#drawChild:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { //View中有兩個draw方法 //這個多參數的draw用於view繪制自身內容 return child.draw(canvas, this, drawingTime); }
View#draw(canvas, this, drawingTime)
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; ... //主要判斷是否有繪制緩存,如果有,直接使用緩存,如果沒有,調用 draw(canvas)方法 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); } } } else if (cache != null) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE) { // no layer paint, use temporary paint to draw bitmap Paint cachePaint = parent.mCachePaint; if (cachePaint == null) { cachePaint = new Paint(); cachePaint.setDither(false); parent.mCachePaint = cachePaint; } cachePaint.setAlpha((int) (alpha * 255)); canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); } else { // use layer paint to draw the bitmap, merging the two alphas, but also restore int layerPaintAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha)); canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); } } }
首先判斷是否已經有緩存,即之前是否已經繪制過一次了,如果沒有,則會調用 draw(canvas) 方法,開始正常的繪制,即上面所說的六個步驟,否則利用緩存來顯示。
這一步也可以歸納為 ViewGroup 繪制過程,它對子 View 進行了繪制,而子 View 又會調用自身的 draw 方法來繪制自身,這樣不斷遍歷子 View 及子 View 的不斷對自身的繪制,從而使得 View 樹完成繪制。
對於自定義 View ,如果需要繪制東西的話,直接重新 onDraw 就可以了。
6. 繪制裝飾
public void onDrawForeground(Canvas canvas) { //繪制滑動指示 onDrawScrollIndicators(canvas); //繪制ScrollBar onDrawScrollBars(canvas); //獲取前景色的Drawable,繪制到canvas上 final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight()); } else { selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); } }
2和5.繪制View的褪色邊緣
當 horizontalEdges 或者 verticalEdges 有一個 true 的時候,表示需要繪制 View 的褪色邊緣:
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
這時候先計算出是否需要繪制上下左右的褪色邊緣和它的參數,然后保存視圖層:
int paddingLeft = mPaddingLeft; final boolean offsetRequired = isPaddingOffsetRequired(); if (offsetRequired) { paddingLeft += getLeftPaddingOffset(); } int left = mScrollX + paddingLeft; int right = left + mRight - mLeft - mPaddingRight - paddingLeft; int top = mScrollY + getFadeTop(offsetRequired); int bottom = top + getFadeHeight(offsetRequired); if (offsetRequired) { right += getRightPaddingOffset(); bottom += getBottomPaddingOffset(); } final ScrollabilityCache scrollabilityCache = mScrollCache; final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; if (verticalEdges && (top + length > bottom - length)) { length = (bottom - top) / 2; } if (horizontalEdges && (left + length > right - length)) { length = (right - left) / 2; } if (verticalEdges) { topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength())); drawTop = topFadeStrength * fadeHeight > 1.0f; bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength())); drawBottom = bottomFadeStrength * fadeHeight > 1.0f; } if (horizontalEdges) { leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength())); drawLeft = leftFadeStrength * fadeHeight > 1.0f; rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength())); drawRight = rightFadeStrength * fadeHeight > 1.0f; } saveCount = canvas.getSaveCount(); int solidColor = getSolidColor(); if (solidColor == 0) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } } else { scrollabilityCache.setFadeColor(solidColor); }
繪制褪色邊緣,恢復視圖層 :
final Paint p = scrollabilityCache.paint; final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { matrix.setScale(1, fadeHeight * bottomFadeStrength); matrix.postRotate(180); matrix.postTranslate(left, bottom); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, bottom - length, right, bottom, p); } if (drawLeft) { matrix.setScale(1, fadeHeight * leftFadeStrength); matrix.postRotate(-90); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, left + length, bottom, p); } if (drawRight) { matrix.setScale(1, fadeHeight * rightFadeStrength); matrix.postRotate(90); matrix.postTranslate(right, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(right - length, top, right, bottom, p); } canvas.restoreToCount(saveCount);
所謂的繪制裝飾,就是指 View 除了背景、內容、子 View 的其余部分,例如滾動條等。
最后附上 View 的 draw 流程:
到此,View 的繪制流程就講完了,下一篇會講自定義 View。