ViewRoot对应于ViewRootImpl类,他是链接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl和DecorView建立关联
DecorView作为顶级的View,一般情况下它内部会包括一个竖直方向的LinearLayout,这个LinearLayout里面具有上下俩个部分(具体跟Android的版本和主题有关),上面是标题栏,下面是内容栏,在Activity中setContentView的布局就是被加在了内容栏之中,内容栏的id为content,如何得到content?可以通过findViewById(android.R.id.content)),如何得到我们在Activity中设置的View,可以content.getChildAt(0);同时DecorView其实是一个FrameLayout,View的事件都是通过DecorView传递给我们的View的
View的绘制流程是从ViewRootImpl的performTraversals方法开始的,他经过measure,layout,draw才能最终将一个View绘制出来,其中measure用来测量View的宽高,layout用来确定View在父容器中的位置,draw负责将View绘制在屏幕上,大致如图:
performTraversals方法会依次调用,performMeasure,performLayout,performDraw三个方法,这三个方法分别完成顶级View的measure,layout和draw这三大流程
其中performMeasure方法会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父元素传入到了子元素中,这样就完成了一次measure过程,接着子元素会重复父元素的measure过程,这样反复就完成View树的遍历,同理performLayout和performDraw的传递流程和performMeasure是类似的,唯一不同的是preformDraw传递过程是在draw方法中通过dispatchDraw来实现的,本质没有区别
MeasureSpec参与了View的measure过程,MeasureSpec很大程度上决定了View的尺寸规格,之所以说很大程度是因为,他还受父View的影响,因为父容器影响了View的MeasureSpec的创建过程
MeasureSpec代表一个32位的int值,高俩位代表SpecMode,低30位代表SpecSize,SpecMode指的是测量模式,SpecSize代表的是某种测量模式下,规格的大小,下面先看一下MeasureSpec的内部常量的一些定义
public static class MeasureSpec {
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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多对象内存分配,为了方便操作,其提供了打包和解包的功能,SpecSize和SpecMode也是int值,一个SpecMode和一个SpecSize可以打包成为一个MeasureSpec,而一个MeasureSpec可以解包为一个SpecSize和一个SpecMode
SpecMode有三类,每一类都代表了特殊的含义,如下所示
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,这种情况一般适用于系统内部,表示一种测量状态
EXACTLY
父容器已经检测出了View所需要的精确大小,这个时候View的最终大小就是SpecSize的值,他对应于LayoutParams中的mach_paraent和具体数值俩种模式
AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现,他对应于LayoutParams中的warp_content
我们给View设置LayoutParams,在View测量的时候,系统将本View设置的LayoutParams和父容器的约束下转换成本View的MeasureSpec,然后根据这个MeasureSpec确定本View的测量后的宽高
另外,对于顶级View(即DecorView)和普通View来说,MeasureSpec的转换过程有所不同,
对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有如下一段代码,他展示了DecorView创建MeasureSpec的过程
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
其中desiredWindowWidth和desiredWindowHeight是指的屏幕尺寸,再看一下getRootMeasureSpec方法的源码
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和MeasureSpec的产生过程就很明确了,其遵守如下规则,根据他的LayoutParams中的宽高参数来划分
对于普通View来说,View的measure过程由ViewGroup传递而来,先看一下ViewGroup的measureChildWithMargins方法源码
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上述方法会调用子View的measure方法,调用子元素的measure方法之前,会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec,从代码来看子元素的MeasureSpec的创建,和父容器的Measure,和自身的LayoutParams有关,此外和View的margin及padding有关,具体情况可以看一下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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这个方法不难理解,它主要的作用是根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec,其中的padding指的是父容器已经占用空间的大小,因此子元素的可用大小为父元素的尺寸减去padding,代码如下:
int size = Math.max(0, specSize - padding);
getChildMeasureSpec方法清楚的展示了普通View 的MeasureSpec的创建规则,为了更清晰的看出getChildMeasureSpec的逻辑,我们提供一个表,对getChildMeasureSpec方法进行梳理
我们再次分析一下图
我们这里并没有讨论UNSPECIFIED模式,因为这种情况主要用于系统内部多次measure的情形,一般来说我们不需要关注
View的工作流程主要是指,measure,layout,draw这三大流程,即测量布局和绘制,其中measure确定了View的测量宽高,layout确定了View的最终宽高和四个顶点的位置,而draw是将View绘制到了屏幕上
measure过程有俩种情况,如果这是一个原始的View,那么通过measure方法就完成了其测量,如果是一个ViewGoup,除了完成自己的测量外,还会去遍历去调用所有子元素的measure方法,各个子元素再去 执行这个流程,下面针对这俩个流程进行分析
View的measure过程
View的measure过程是通过measure方法完成的,measure方法是一个final类型的方法,不能够子类被重写,在View的measure方法中会调用View的onMeasure方法,我们只需看onMeasure方法的实现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension方法会设置View宽高的测量值,我们看一下getDefaultSize这个方法
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;
}
getDefaultSize这个方法的逻辑很简单,对于我们来说只需要看AT_MOST和EXACTLY这俩种情况,简单理解getDefaultSize返回的就是MeasureSpec中的SpecSize,而这个SpecSize就是View测量后的大小,这里多次提到测量大小,因为View的最终大小是在layout阶段确定的,所以这里加以区分
至于UNSPECIFIED这种情况,一般用于系统内部的测量过程,这种情况下View的大小为getDefaultSize
的第一个参数size,即宽高分别为getSuggestedMinimumWidth
和getSuggestedMinimumHeight
这俩个方法的返回值,看一下他们的源码
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
这里只分析getSuggestedMinimumWidth
的实现,从代码中可以看出如果View没有设置背景,那么View的宽度是mMinWidth
,而mMinWidth
代表android:minWidth
这个属性所指定的值,如果不指定这个属性则默认为0,如果View设置了背景,则View的宽度为max(mMinWidth, mBackground.getMinimumWidth())
,那么我们研究一下mBackground.getMinimumWidth()
是什么
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
getMinimumHeight方法返回的是Drawadle的原始宽度,前提是有原始宽度,ShapeDrawable无原始宽度,BitmapDrawable有原始宽度
现在我们总结一下getSuggestedMinimumWidth的逻辑
android:minWidth
这个属性值,不指定则默认为0android:minWidth
这个属性值和背景最小宽度的最大值从getDefaultSize来看View的宽高由SpecSize决定,我们可以得出如下结论,直接继承View的自定义控件,需要重写onMeasure方法,并设置wrap_content时的自身大小,否则用wrap_content相当于用mach_parent,为什么呢?
从上方的表我们可以知道子View的MeasureSpec,是根据自身的LayoutParams和父容器的MeasureSpec决定的,当子View的LayoutParams是wrap_content时,不管父容器的MeasureSpec是什么,子View的SpecMode都是AT_MOST,这种情况下View的SpecSize是父容器剩余空间的大小,这种情况下的布局和mach_parent是一样的,如何解决这种问题?
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 80;
int height = 80;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heigthMode = MeasureSpec.getMode(heightMeasureSpec);
int heigthSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heigthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(width, height);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(width, heigthSize);
} else if (heigthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, height);
}
}
我们给View指定一个默认的宽高,当设置warp_content的时候直接设置此宽高,对于非warp_content的情况我们需要沿用系统的测量值,至于宽高的默认值大小没有规定依据,TextView和其他控件对warp_content情况均作了特殊处理
VIewGroup的measure过程
对于ViewGroup来说,除了需要执行自己的measure过程,还需要遍历所有的子元素measure方法,各个子元素再去递归执行这个流程,和View不同的是,ViewGroup没有重写View的onMeasure方法,但是它提供了一个measureChildren
的方法,源码如下所示:
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);
}
}
}
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);
}
最终就是取出child的LayoutParams,然后在通过getChildMeasureSpec
来创建子元素的MeasureSpec,然后调用子View的measure方法去测量
ViewGroup没有具体的测量过程,因为ViewGroup是一个抽象类,其测量的onMeasure方法需要各个子类去去实现,为什么不能统一实现呢?因为每个ViewGroup的子类都有不同的布局特性,这就导致他们的测量过程有所差异,比如LineraLayout和RelativeLayout的布局特性就不一样,所以无法做统一实现,下面我们分析一下LineraLayout的onMeasure实现
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
上面的代码很简单,我们分析一下竖直情况下的代码measureVertical
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
....
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
这段代码表示,系统会遍历子元素并且调用measureChildBeforeLayout
方法,这个方法内部会调用View的measure方法,这样各个子View都会依次进入measure过程,并且系统会用mTotalLength记录LineraLayout竖直方向的高度,每测量一个子元素,mTotalLength就会增加,当子元素处理完,LineraLayout会测量自己的大小,源码是
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
上述代码说明,当子元素测量完毕后,LineraLayout会根据子元素的大小测量自己的大小,针对LineraLayout他在水平方向遵循View的测量过程,在竖直方向的测量有所不同,具体来说如果是match_parent或者具体数值,他的测量过程和View一致,即高度为SpecSize,如果他的布局是warp_content,那么高度是所有子元素占用的高度的总和,但是他依然不能超过父元素的剩余空间,当然还要考虑padding,看下源码
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
View的measure过程是三大流程最复杂的一个,measur完成以后就可以获取测量宽高,需要注意的是有些极端情况下,需要measure多次才可以确定测量宽高,所以尽量不要在onMeasure中拿宽高,在onLayout里面获取会比较准确
layout确定了View的四个顶点的位置和最终的宽高,如果是ViewGroup,当ViewGroup的位置确定后,他在onLayout遍历子元素调用子元素的layout方法,在layout方法中onLayout又会被调用,如此循环,layout方法确定View本身的位置,而onLayout方法确定所有子元素的位置,先看一下View的layout方法
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);
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;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
}
layout的大致流程如下,首先会通过setFrame方法来设定View的四个顶点的值,及初始化mLeft,mTop,mRight,mBottom的值,View的四个顶点一旦确定,View在父容器的位置就确定了,接着会调用onLayout方法,这个方法是为了父容器确定子元素的位置,onLayout的具体实现和具体的布局有关,View和ViewGroup均没有真正实现onLayout方法,我们看一下LineraLayout的onLayout方法的实现
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);
}
}
这里选择layoutVertical进行分析
void layoutVertical(int left, int top, int right, int bottom) {
...
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();
...
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);
}
}
}
可以看到此方法会遍历子元素并调用setChildFrame方法为子元素指定相应的位置,其中childTop会不断的变大,这就意味着后面的子元素会放在靠下的位置,这刚好符合LineraLayout竖直的属性,setChildFrame只是调用了子元素的layout方法,父元素通过layout确定自己的位置后,通过onLayout调用子元素的layout方法,如此循环,完成整个View树的layout,setChildFrame的源码:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
我们注意到setChildFrame中的width和height就是View的测量宽高,如下代码可以看出
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);
而在layout中会通过setFrame方法去设置四个顶点的宽高,如下代码
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
View的测量宽高和最终宽高有什么区别
我们看一下getWidth和getMeasureWidth有什么区别,我们先看一下getWidth和getHeight的源码
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
通过上面源码我们可以知道,其实View默认情况下测量宽高和最终宽高大小是一样的,只不过测量宽高是measure过程形成的,而最终宽高是在layout过程形成的,赋值的时机不同,measure比较早,在实际的开发中,我们可以认为测量宽高等于实际的宽高,但是某些情况下他们不同,比如:
重写View的layout方法
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r+100, b+100);
}
这样就会导致测量宽高比实际宽高小100px,这样会导致View显示不正常没有实际的意义,但是也证明了有不相等的情况
draw过程比较简单,他的作用是把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);
}
// 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;
}
View绘制过程是通过dispatchDraw方法向子View 传递的,dispatchDraw会遍历所有的子View调用子View的draw方法,这样draw时间就一层一层传了下去,View有一个特殊的方法setWillNotDraw
/**
* 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不需要绘制任何内容,那么设置这个标记为true后,系统会进行相应的优化,默认情况下View没有启用这个标志位,但是ViewGroup会默认启用这个标志位,当我们我们的自定义控件继承自ViewGroup时本身不具备绘制功能,就可以开启这个标志位,如果需要通过onDraw绘制,那么就要关闭这个标志位