View繪製流程(二)

View的佈局
當 ViewRootImpl 的 performTraversals
中 performMeasure
執行完成以後會接著執行 performLayout
, ViewRootImpl 呼叫 performLayout
執行Window對應的View的佈局。
- ViewRootImpl的performLayout。
- DecorView(FrameLayout)的layout方法。
- DecorView(FrameLayout)的onLayout方法。
- DecorView(FrameLayout)的layoutChildren方法。
- DecorView(FrameLayout)的所有子View的Layout。
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { /*******部分程式碼省略**********/ //View的佈局 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { /*******部分程式碼省略**********/ final View host = mView; /*******部分程式碼省略**********/ try { //呼叫View的Layout方法進行佈局 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false; //在ViewRootImpl進行佈局的期間,Window內的View自己進行requestLayout int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { 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佈局,最終回撥到ViewRootImpl的requestLayout進行重新測量、佈局、繪製 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) { 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); /*******部分程式碼省略**********/ //請求對該View佈局,最終回撥到ViewRootImpl的requestLayout進行重新測量、佈局、繪製 view.requestLayout(); } } }); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; } }
layout
方法接收四個引數,這四個引數分別代表相對Parent的左、上、右、下座標。而且還可以看見左上都為0,右下分別為上面剛剛測量的 width 和 height 。
public void layout(int l, int t, int r, int b) { ...... //實質都是呼叫setFrame方法把引數分別賦值給mLeft、mTop、mRight和mBottom這幾個變數 //判斷View的位置是否發生過變化,以確定有沒有必要對當前的View進行重新layout boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //需要重新layout if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //回撥onLayout onLayout(changed, l, t, r, b); ...... } ...... }
類似 measure 過程, layout
呼叫了 onLayout
方法, 這裡需要注意的是 layout
方法可以被子類重寫, 下面看一下 View 的 onLayout
方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
竟然是一個空的方法。,那麼就看一下 ViewGroup 的 layout
方法。
@Override public final void layout(int l, int t, int r, int b) { if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) { if (mTransition != null) { mTransition.layoutChange(this); } //呼叫View的layout方法 super.layout(l, t, r, b); } else { mLayoutCalledWhileSuppressed = true; } }
本質還是呼叫 View 的 layout
方法, 這裡需要注意的是ViewGroup的layout方法是不能被子類重寫的。
接下來看下 ViewGroup 的 onLayout
方法。
@Override protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
ViewGroup的 onLayout()
方法竟然是一個抽象方法,這就是說所有 ViewGroup 的子類都必須重寫這個方法。所以在自定義ViewGroup控制元件中, onLayout
配合 onMeasure
方法一起使用可以實現自定義View的複雜佈局。自定義 View 首先呼叫 onMeasure
進行測量,然後呼叫 onLayout
方法動態獲取子View和子 View 的測量大小,然後進行layout佈局。過載onLayout的目的就是安排其children在父View的具體位置,過載 onLayout
通常做法就是寫一個for迴圈呼叫每一個子檢視的 layout(l, t, r, b)
函式,傳入不同的引數 l, t, r, b
來確定每個子檢視在父檢視中的顯示位置。
既然 View 的 onLayout
方法為空方法, ViewGroup 的 onLayout
方法為抽象方法,下面以 ViewGroup 的子類 LinearLayout 為例。
public class LinearLayout extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } } }
LinearLayout的 layout
過程是分 Vertical 和 Horizontal 的,這個就是 xml 佈局的 orientation
屬性設定的。
void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go //計算父視窗推薦的子View寬度 final int width = right - left; //計算父視窗推薦的子View右側位置 int childRight = width - mPaddingRight; // Space available for child //child可使用空間大小 int childSpace = width - paddingLeft - mPaddingRight; //通過ViewGroup的getChildCount方法獲取ViewGroup的子View個數 final int count = getVirtualChildCount(); //獲取Gravity屬性設定 final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //依據majorGravity計算childTop的位置值 switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } //重點!!!開始遍歷 for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { //LinearLayout中其子檢視顯示的寬和高由measure過程來決定的,因此measure過程的意義就是為layout過程提供檢視顯示範圍的參考值 final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); //獲取子View的LayoutParams final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //依據不同的absoluteGravity計算childLeft位置 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; //通過垂直排列計算調運child的layout設定child的位置 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }
實質都是呼叫 setFrame
方法把引數分別賦值給 mLeft、mTop、mRight和mBottom 這幾個變數。
從上面分析的 ViewGroup 子類 LinearLayou t的 onLayout
實現程式碼可以看出,一般情況下 layout
過程會參考 measure
過程中計算得到的 mMeasuredWidth 和 mMeasuredHeight 來安排子View在父View中顯示的位置,但這不是必須的, measure
過程得到的結果可能完全沒有實際用處,特別是對於一些自定義的 ViewGroup ,其子 View 的個數、位置和大小都是固定的,這時候我們可以忽略整個 measure
過程,只在 layout
方法中傳入的4個引數來安排每個子 View 的具體位置。
到這裡就不得不提 getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()
這兩對方法之間的區別(上面分析 measure
過程已經說過 getMeasuredWidth()、getMeasuredHeight()
必須在 onMeasure
之後使用才有效)。可以看出來 getWidth()與getHeight()
方法必須在 layout(int l, int t, int r, int b)
執行之後才有效。那我們看下View原始碼中這些方法的實現吧,如下:
public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; } public final int getMeasuredHeight() { return mMeasuredHeight & MEASURED_SIZE_MASK; } public final int getWidth() { return mRight - mLeft; } public final int getHeight() { return mBottom - mTop; } public final int getLeft() { return mLeft; } public final int getRight() { return mRight; } public final int getTop() { return mTop; } public final int getBottom() { return mBottom; }
mMeasuredWidth
是一個8位的十六進位制數,高兩位代表 Mode 跟 MEASURED_SIZE_MASK
按位 & 後獲得測量後的寬度。
View佈局總結
View.layout
方法可被過載, ViewGroup.layout
為 final 的不可過載, ViewGroup.onLayout
為 abstract 的,子類必須過載實現自己的位置邏輯。 View.onLayout
方法是一個空方法。
measure
操作完成後得到的是對每個 View 經測量過的 measuredWidth和measuredHeight , layout
操作完成之後得到的是對每個 View 進行位置分配後的 mLeft、mTop、mRight、mBottom
,這些值都是相對於父 View 來說的。
onLayout
中最終迴圈呼叫子 View 的 setFrame
方法來設定 mLeft、mTop、mRight和mBottom 的值。
getMeasuredWidth()、getMeasuredHeight()
必須在 onMeasure
之後使用才有效; getWidth()與getHeight()
方法必須在 layout(int l, int t, int r, int b)
執行之後才有效。
View的繪製
ViewRootImpl呼叫 performDraw
執行 Window 對應的 View 的佈局。
performDraw draw drawSoftware draw dispatchDraw drawChild draw
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { /*******部分程式碼省略**********/ //View的繪製 private void performDraw() { /*******部分程式碼省略**********/ try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } /*******部分程式碼省略**********/ } //進行繪製 private void draw(boolean fullRedrawNeeded) { /*******部分程式碼省略**********/ //View上新增的Observer進行繪製事件的分發 mAttachInfo.mTreeObserver.dispatchOnDraw(); if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { /*******部分程式碼省略**********/ //呼叫Window對應的ViewRootImpl的invalidate方法 mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this); } else { /*******部分程式碼省略**********/ //繪製Window if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); } } void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); } } /** * @return true if drawing was successful, false if an error occurred */ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { /*******部分程式碼省略**********/ try { /*******部分程式碼省略**********/ try { canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //View繪製 mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } } finally { /*******部分程式碼省略**********/ } return true; } }
由於 ViewGroup 沒有重寫 View 的 draw
方法,所以直接看 View.draw
方法:
public void draw(Canvas canvas) { ...... // 1. 繪製背景 ...... if (!dirtyOpaque) { drawBackground(canvas); } ...... //2.繪製View的內容 if (!dirtyOpaque) onDraw(canvas); //3.對當前View的所有子View進行繪製,如果當前的View沒有子View就不需要進行繪製。 dispatchDraw(canvas); ...... //4.對View的滾動條進行繪製。 onDrawScrollBars(canvas); ...... }
1.對View的背景進行繪製。
private void drawBackground(Canvas canvas) { //獲取xml中通過android:background屬性或者程式碼中setBackgroundColor()、setBackgroundResource()等方法進行賦值的背景Drawable final Drawable background = mBackground; ...... //根據layout過程確定的View位置來設定背景的繪製區域 if (mBackgroundSizeChanged) { background.setBounds(0, 0,mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; rebuildOutline(); } ...... //呼叫Drawable的draw()方法來完成背景的繪製工作 background.draw(canvas); ...... }
可以看出 View 背景是一個 Drawable ,繪製背景最終呼叫的是 Drawable.draw
2.對View的內容進行繪製。
protected void onDraw(Canvas canvas) { }
View.onDraw
方法為空方法, ViewGroup 也沒有重寫該方法。因為每個 View 的內容部分是各不相同的,所以需要由子類去實現具體邏輯。
3.對當前View的所有子View進行繪製,如果當前的View沒有子View就不需要進行繪製。
View.dispatchDraw()
方法是一個空方法,如果 View 包含子類需要重寫他,所以我們看下 ViewGroup.dispatchDraw
方法原始碼:
@Override protected void dispatchDraw(Canvas canvas) { ...... final int childrenCount = mChildrenCount; final View[] children = mChildren; ...... for (int i = 0; i < childrenCount; i++) { ...... if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } ...... // Draw any disappearing views that have animations if (mDisappearingChildren != null) { ...... for (int i = disappearingCount; i >= 0; i--) { ...... more |= drawChild(canvas, child, drawingTime); } } ...... }
該方法內部會遍歷每個子 View ,然後呼叫 drawChild()
方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
4.對View的滾動條進行繪製。
protected final void onDrawScrollBars(Canvas canvas) { final ScrollabilityCache cache = mScrollCache; if (cache != null) { int state = cache.state; if (state == ScrollabilityCache.OFF) { return; } boolean invalidate = false; ......... final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled(); final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden(); // Fork out the scroll bar drawing for round wearable devices. if (mRoundScrollbarRenderer != null) { if (drawVerticalScrollBar) { final Rect bounds = cache.mScrollBarBounds; getVerticalScrollBarBounds(bounds, null); mRoundScrollbarRenderer.drawRoundScrollbars( canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds); if (invalidate) { invalidate(); } } } else if (drawVerticalScrollBar || drawHorizontalScrollBar) { final ScrollBarDrawable scrollBar = cache.scrollBar; if (drawHorizontalScrollBar) { scrollBar.setParameters(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(), false); final Rect bounds = cache.mScrollBarBounds; getHorizontalScrollBarBounds(bounds, null); onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top, bounds.right, bounds.bottom); if (invalidate) { invalidate(bounds); } } if (drawVerticalScrollBar) { scrollBar.setParameters(computeVerticalScrollRange(), computeVerticalScrollOffset(), computeVerticalScrollExtent(), true); final Rect bounds = cache.mScrollBarBounds; getVerticalScrollBarBounds(bounds, null); onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top, bounds.right, bounds.bottom); if (invalidate) { invalidate(bounds); } } } } }
可以看見其實任何一個 View 都是有(水平垂直)滾動條的,只是一般情況下沒讓它顯示而已。
View的invalidate和postInvalidate方法原始碼分析
View中的 invalidate
方法有很多的過載,最終都會呼叫 invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate)
方法( ViewGroup 沒有重寫這些方法)
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ...... // 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); //傳遞調運Parent ViewGroup的invalidateChild方法 p.invalidateChild(this, damage); } ...... }
View.invalidateInternal
方法實質是將要重新整理區域直接傳遞給了父 ViewGroup的invalidateChild
方法,在該方法中,呼叫父 View的invalidateChild
,這是一個從當前向上級父View回溯的過程,每一層的父View都將自己的顯示區域與傳入的重新整理Rect做交集 。所以我們看下 ViewGroup的invalidateChild
方法,原始碼如下:
public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; ...... do { ...... //迴圈層層上級調運,直到ViewRootImpl會返回null parent = parent.invalidateChildInParent(location, dirty); ...... } while (parent != null); }
這個過程最後傳遞到 ViewRootImpl的invalidateChildInParent
方法結束,所以我們看下 ViewRootImpl的invalidateChildInParent
方法,如下:
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { ...... //View調運invalidate最終層層上傳到ViewRootImpl後最終觸發了該方法 scheduleTraversals(); ...... return null; }
最終呼叫了 scheduleTraversals
方法,該方法會呼叫 ViewRootImpl.performTraversals
方法,重新繪製。
invalidate
該方法只能在UI Thread中執行,其他執行緒中需要使用 postInvalidate
方法
postInvalidate
方法最終會呼叫 ViewRootImpl 類的 dispatchInvalidateDelayed
方法,原始碼如下:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); }
ViewRootImpl類的 Handler 傳送了一條 MSG_INVALIDATE
訊息,繼續追蹤這條訊息的處理可以發現:
public void handleMessage(Message msg) { ...... switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ...... } ...... }
最終在UI執行緒中呼叫了 View 的 invalidate
方法。
直接呼叫 invalidate
方法.請求重新 draw ,但只會繪製呼叫者本身。因為其他的 View 狀態沒有變化的話,是不會執行對應的繪製方法的。
在視窗上的View都有一個ViewRootImpl作為它的Parent,處理View的佈局、事件處理等。