View的繪製流程梳理
MeasureSpec
由於在繪製過程中,這個變量作爲參數,與所有的View的measure都有關係。
MeasureSpec是一個int值,分爲兩個部分,高兩位代表SpecMode,緊接着的30位代表着SpecSize。以下是其計算過程:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
當然,有打包的方法也會有解包的方法,我們可以通過getMode()和getSize()分別來獲取SpecMode和SpecSize。
其中SpecMode分爲三種:
UNSPECIFIED
ParentView 對ChildView的大小沒有任何約束,ChildView的大小可以爲任意值。
EXACTLY
ParentView 已經檢測到ChildView所需要的精確的大小,而ChildView的最終大小也就是MeasureSpec指定的值。
AT_MOST
ParentView 爲ChildView指定了一個上限值,ChildView的大小可以在這個範圍內變化。
MeasureSpec的來源
MeasureSpec最初是在頂級View中創建的,頂級View(即DecorView)的MeasureSpec是由窗口的尺寸和其自身的LayoutParams決定的。其創建過程在ViewRootImp中的measurehierarchy方法裏:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
其中desiredWindowWidth 和 desiredWindowHeight是屏幕的尺寸,而lp就是DecorView自身的layoutParams,來自ViewRootImp中一個成員變量mWindowAttributes。接下來看一下getRootMeasureSpec的邏輯:
private static int getRootMeasureSpec(int windowSize,int rootDimension){
int measureSpec;
switch(rootDimension){
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通過上訴代碼我們得出,對於DecorView來說,其SpecMode主要是自身的layoutParams來決定的,當其爲MATCH_PARENT或者其他指定的值時,SpecMode爲EXACTLY,當其爲WRAP_CONTENT,SpecMode爲AT_MOST。對於SpecSize,當不指定具體值的時候,我們就使用WindowSize來作爲MeasureSpec的一部分,否則,使用指定的值。
對於普通View來說,其Measure是由ParentView的MeasureSpec和其自身的Layoutparams來決定,具體的邏輯是在ViewGroup的getChildMeasureSpec()中實現的。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
方法中的三個參數分別爲:spec:當前Viewgroup的measureSpec的值;padding:ViewGroup已經佔用的大小;childDimension:在當前的尺寸下,子View想要的大小,也就是子View的LayoutParams。上面的代碼可以簡單用 一張表格來表示:
EXACTLY | AT_MOST | UNSPECIFIED | |
---|---|---|---|
MATCH_PARENT | SpecSize = size SpecMode = EXACTLY | SpecSize =size SpecMode = AT_MOST | SpecSize = 0 SpecMode=UNSPECIFIED |
WRAP_CONTENT | SpecSize =size SpecMode = AT_MOST | SpecSize =size SpecMode = AT_MOST | SpecSize = 0 S pecMode = UNSPECIFIED |
exactly size | SpecSize=childDimension Specmode =EXACTLY | SpecSize=childDimension Specmode =EXACTLY | SpecSize=childDimension Specmode =EXACTLY |
Measure
View的繪製過程是從ViewRootImp中的performTraversals()開始,其中分別調用performMeasure(),performLayout(),以及performDraw(),同時也調用起了measureHierarchy方法。而之前在上文中已經說過,該方法會去設置DecorView的MeasureSpec,並且調用performMeasure()方法。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這裏調用的是DecorView的measure方法,DecorView作爲頂級View,其內部一般包含一個LinearLayout,其中又包含上下兩個部分,一個titlebar,一個是content。而Activity中的setContentView就是將我們的佈局填充到Content中。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nrlqEjkn-1584976185742)(C:\Users\15783\Desktop\DecorView.webp.jpg)]
下面是View的measure()的代碼
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
/**
* Return true if o is a ViewGroup that is laying out using optical bounds.
* @hide
*/
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
通過註釋我們可以知道,一個View的具體測量是在onMeasure中做的。而onMeasure的代碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
其中getDefaultSize()和getSuggestedMinimumWidth()的代碼如下:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getSuggestedMinimumWidth最終得到的是這個View中是否有背景,如果沒有就返回mMinWidth,而這個值對應着android:minWidth,一般默認爲0。否則的話,就取背景的最小寬度和mMinWidth兩者的最大值。
getDefaultSize的邏輯也很好理解,如果specMode爲AT_MOST或者EXACTLY,就取ParentView(屏幕)能給到的最大值,否則的話,就取getSuggestedMinimumWidth返回的值。結合之前DecorView的MeasureSpec的來源,我們可以發現,其中SpecMode就只有兩種模式,AT_MOST或者EXACTLY,所以specSize決定了DecorView的寬和高。但是這裏也存在一個問題,那就是WRAP_CONTENT和MATCH_PARENT,表現爲一致了,所以我們在自定義View的時候,重寫onMeasure時,要針對AT_MOST有其他的設計。
整個View 的measure過程是層層遞歸的,在DecorView的measure過程結束之後,會調用起ViewGroup的measure過程,然後在開始子View的measure。
ViewGroup的measure過程主要是對子View的一個遞歸measure的過程,首先是measureChildren:
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
這部分的d代碼很好理解,逐一遍歷View樹的每一個節點,並且讓其執行 measure動作,如果子View是ViewGroup,那麼會繼續執行measureChildren()。
Layout
layout的調用時在ViewRootImp中的PerformTraversal開始被調用的,VIew的layout過程也是和View的Measure過程相似,是一個遞歸的過程。
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
這裏就是傳入四個頂點的座標作爲參數,接下來我們先看view的layout函數:
/**
* Assign a size and position to a view and all of its
* descendants
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
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);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED){
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo o = 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);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", 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;
if (mDisplayList != null) {
mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
if (mTransformationInfo != null) {
mTransformationInfo.mMatrixDirty = true;
}
}
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// 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;
}
return changed;
}
首先判斷佈局與之前是否有變化,如果有變化或者是調用了requesLayout(),就調用onLayout(),判斷佈局是否有變化的邏輯也比較簡單,主要是判斷四個頂點大小是否有區別,同時在setFrame()中不僅僅判斷了佈局是否變化,同時也將mLeft,mRight,mTop,mBottom賦值。在調用 onLayout()之後,就會調用OnLayoutChangeListener.onLayoutChange()。
但是這裏有一個值得注意的點就是,我們想要得到最終View的寬和高的時候,必須等待setFrame()執行完,因爲View.getHeight()以及View.getWidth()是通過mLeft,mRight,mTop,mBottom這四個值來得到最終寬和高的。因爲存在測量得到的寬和高並不一定最後原封不動作爲參數傳入layout()中,所以measure得到的寬和高不一定是最終值,這樣的話getMeasuredHeight()以及getMeasuredWidth()獲得的值是會存在會與View.getHeight()以及View.getWidth()不等的情況。同時參照上面的代碼,我們可以在onLayoutChange()中調用getWidth()和getHeight()來獲取。
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;
}
再來看ViewGroup的layout函數,最終也是調用到View的layout()中,邏輯沒什麼區別。
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;
}
}
這裏需要注意的是,在layout過程中,我們的最終目的是獲取View的最終四個頂點的值,所以View的onLayout方法全部爲空,並且不要求子類一定要去實現它,比如:ImageView中就沒有實現onLayout,而textView的onlayout執行的也是跟這四個頂點的確認沒有關係,因爲View的layout()已經將其實現了。但是ViewGrop中的子View不一樣,因爲需要計算每個子view的四個頂點的位置,所以VIewGroup的onlayout()是一個抽象方法,這要求所有VIewGroup的子類需要去實現這個方法,其中需要執行的操作一定是計算其所有子View的四個頂點的位置,我們可以用LinearLayout的onlayout()來舉例:
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);
}
}
}
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
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
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) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
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);
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;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
Draw
Draw的流程如下:
其中,View的draw()的代碼主要如下:
public void draw(Canvas canvas) {
......
/*
* 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
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// 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
......
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);
}
......
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}
大概分爲四步:
(1) 繪製背景:drawBackground(canvas);
(2)繪製自己:onDraw(canvas);
(3)繪製事件分發:dispatchDraw(canvas);
(4)繪製裝飾:onDrawScrollBars(canvas);
如此,從頂層 DecorView 的 draw 方法開始,然後調用 dispatchDraw 方法循環遍歷繪製子元素,如果子元素是繼承了 ViewGroup ,就再次循環調用 dispatchDraw 方法,一層層往下遞歸調用,直到每一個子元素都被繪製完成,整個 draw 流程也就結束了。
WillNotDraw
在 View 中有一個方法是 setWillNotDraw:
//View 類
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
當一個View不需要draw的時候,就可以將mViewFlags設置爲true,這樣的話系統會進行相應的優化,比如ViewGroup就不需要draw這一個步驟,所以就會默認使用setWillNotDraw(true)來完成這一步優化。