本篇文章主要内容是:详细讲述View的测量(Measure)流程从ViewRootImpl#performTraversals说起
先来看看performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)这个方法,它传入两个参数,分别是childWidthMeasureSpec和childHeightMeasure,那么这两个参数代表什么意思呢?要想了解这两个参数的意思,我们就要先了解MeasureSpec。
MeasureSpec
其中,测量模式(Mode)的类型有3种:UNSPECIFIED、EXACTLY 和
AT_MOST。具体如下:
对于每一个View,包括DecorView,都持有一个MeasureSpec,而该MeasureSpec则保存了该View的尺寸规格。
在View的测量流程中,通过makeMeasureSpec来保存宽高信息,在其他流程通过getMode或getSize得到模式和宽高。
那么问题来了,上面提到MeasureSpec是LayoutParams和父容器的模式所共同影响的,那么,对于DecorView来说,它已经是顶层view了,没有父容器,那么它的MeasureSpec怎么来的呢?
是根据window的窗口大小而来。回到ViewRootImpl#performTraversals
根据不同的模式来设置MeasureSpec,如果是LayoutParams.MATCH_PARENT模式,则是窗口的大小,WRAP_CONTENT模式则是大小不确定,但是不能超过窗口的大小.默认强制设置为窗口大小。
到了这里,我们就为顶级的DecorView设置了MeasureSpec,它代表着根View的规格、尺寸,在接下来的measure流程中,就是根据已获得的根View的MeasureSpec来逐层测量各个子View。
ViewRootImpl#performTraversals
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
try {
//调用了View#measure 方法,childWidthMeasureSpec 和childHeightMeasureSpec 是我们刚刚调用getRootMeasureSpec 生成的
//调用了View#measure 方法,childWidthMeasureSpec 和childHeightMeasureSpec 是我们刚刚调用getRootMeasureSpec 生成的
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
Trace.traceEnd(Trace.TRACE_TAG_VIEW
方法很简单,直接调用了mView.measure,这里的mView就是DecorView,也就是说,从顶级View开始了测量流程,那么我们直接进入measure流程。
由于DecorView继承自FrameLayout,是PhoneWindow的一个内部类,而FrameLayout没有measure方法,因此调用的是父类View的measure方法,我们直接看它的源码,
View#measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
if (optical != isLayoutModeOptical(mParent)) {
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
heightMeasureSpec != mOldHeightMeasureSpec) {
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
mMeasureCache.indexOfKey(key);
//如果我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure
//如果我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure
if (cacheIndex < 0 || sIgnoreMeasureCache) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else{
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
可以看到,调用到下一个关键函数onMeasure。由于DecorView是View子类,因此它实际上调用的是DecorView#onMeasure方法.
@Override
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final boolean isPortrait =
final boolean isPortrait =
getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; //是否是竖向
getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; //是否是竖向
//获取高宽的模式
//获取高宽的模式
final int widthMode = getMode(widthMeasureSpec);
final int widthMode = getMode(widthMeasureSpec);
final int heightMode = getMode(heightMeasureSpec);
final int heightMode = getMode(heightMeasureSpec);
//.....中间依据window的高宽,进行了很多判断,是否需要重新计算宽度和高度
//.....中间依据window的高宽,进行了很多判断,是否需要重新计算宽度和高度
if (measure) {
if (measure) {
//调用到了FrameLayout的onMeasure
//调用到了FrameLayout的onMeasure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
View有非常多的子类容器:FrameLayout、 RelativeLayout、LinearLayout。
FrameLayout#messure
@Override
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取子View的个数
//获取子View的个数
int count = getChildCount();
int count = getChildCount();
// 判断当前布局的宽高是否是wrap_content,如果是则置measureMatchParentChildren为true.后续需要再次测量
// 判断当前布局的宽高是否是wrap_content,如果是则置measureMatchParentChildren为true.后续需要再次测量
final boolean measureMatchParentChildren =
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
mMatchParentChildren.clear();
int maxHeight = 0;
int maxHeight = 0;
int maxWidth = 0;
int maxWidth = 0;
int childState = 0;
int childState = 0;
//遍历所有类型不为GONE的子View
//遍历所有类型不为GONE的子View
for (int i = 0; i < count; i++) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
if (mMeasureAllChildren || child.getVisibility() != GONE) {
对每一个子View进行测量, 依次往下层子View调用
对每一个子View进行测量, 依次往下层子View调用
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性
//寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性
//那么它的大小取决于子View中的最大者
//那么它的大小取决于子View中的最大者
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
childState = combineMeasuredStates(childState, child.getMeasuredState());
//如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加
//如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加
//宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响
//宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响
if (measureMatchParentChildren) {
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
mMatchParentChildren.add(child);
// Account for padding too
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
final Drawable drawable = getForeground();
if (drawable != null) {
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
//保存宽度和高度,必须要调用的方法
//保存宽度和高度,必须要调用的方法
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
childState << MEASURED_HEIGHT_STATE_SHIFT));
//对所有match_parent的子View进行重新计算高度和宽度
//对所有match_parent的子View进行重新计算高度和宽度
count = mMatchParentChildren.size();
count = mMatchParentChildren.size();
if (count > 1) {
if (count > 1) {
for (int i = 0; i < count; i++) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
/**
/**
* 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:
* 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:
* 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:
* 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:
* 对于子View来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度
* 对于子View来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度
* 减去padding和margin后剩下的空间。
* 减去padding和margin后剩下的空间。
**/
**/
final int childWidthMeasureSpec;
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
width, MeasureSpec.EXACTLY);
} else {
else {
/**
/**
* 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
* 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
* SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式
* SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式
*
*
* 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
* 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
* SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式
* SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式
* #getChildMeasureSpec
* #getChildMeasureSpec
*/
*/
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.leftMargin + lp.rightMargin,
lp.width);
lp.width);
final int childHeightMeasureSpec;
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
height, MeasureSpec.EXACTLY);
} else {
else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.topMargin + lp.bottomMargin,
lp.height);
lp.height);
//重新计算后的width 和 height 重新测量
//重新计算后的width 和 height 重新测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
measureChildWithMargins
protected void measureChildWithMargins(View child,
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
+ heightUsed, lp.height);
//调用View的 measure方法,然后是child#onMeasure
//调用View的 measure方法,然后是child#onMeasure
//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
// 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
// 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
让我们看看getChildMeasureSpec 方法,
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
* for one dimension (height or width) of one child view.
*
*
* The goal is to combine information from our MeasureSpec with the
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
* layout given an exact size.
*
*
* @param spec The requirements for this view
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* @param childDimension How big the child wants to be in the current
* dimension
* dimension
* @return a MeasureSpec integer for the child
* @return a MeasureSpec integer for the child
*/
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父容器的模式
int specMode = MeasureSpec.getMode(spec); //父容器的模式
int specSize = MeasureSpec.getSize(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding); //父容器提供的大小
int size = Math.max(0, specSize - padding); //父容器提供的大小
int resultSize = 0;
int resultSize = 0;
int resultMode = 0;
int resultMode = 0;
switch (specMode) {
switch (specMode) {
// Parent has imposed an exact size on us
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
if (childDimension >= 0) {
resultSize = childDimension;
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
// Child wants to be our size. So be it.
resultSize = size;
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// Child wants to determine its own size. It can't be
// bigger than us.
// bigger than us.
resultSize = size;
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
resultMode = MeasureSpec.AT_MOST;
break;
break;
// Parent has imposed a maximum size on us
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
if (childDimension >= 0) {
// Child wants a specific size... so be it
// Child wants a specific size... so be it
resultSize = childDimension;
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
// Constrain child to not be bigger than us.
resultSize = size;
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// Child wants to determine its own size. It can't be
// bigger than us.
// bigger than us.
resultSize = size;
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
resultMode = MeasureSpec.AT_MOST;
}
break;
break;
// Parent asked to see how big we want to be
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
if (childDimension >= 0) {
// Child wants a specific size... let him have it
// Child wants a specific size... let him have it
resultSize = childDimension;
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// Child wants to be our size... find out how big it should
// be
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// Child wants to determine its own size.... find out how
// big it should be
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
break;
}
//noinspection ResourceType
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
View#messure小结
-
如果父View的MeasureSpec 是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。
-
如果父View的MeasureSpec 是AT_MOST,说明父View的大小是不确定,最大的大小是MeasureSpec 的size值,不能超过这个值。
-
如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示没有任何束缚和约束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束
通过以上代码分析,我们可以得出在FrameLayout中,如果子View的高或者宽设置成为了match_parent 其messure会被调用两次。
第一次是为了获取最大的高和宽。
第二次是为了给子View赋予精确的高宽值。
让我们继续下一步:
ViewRootImpl#performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
int desiredWindowHeight) {
mLayoutRequested = false;
mLayoutRequested = false;
mScrollMayChange = true;
mScrollMayChange = true;
mInLayout = true;
mInLayout = true;
final View host = mView;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
try {
//进入View的Layout阶段
//进入View的Layout阶段
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// 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
// 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.
// a full request/measure/layout pass to handle this situation.
//如果在布局期间,重新发起了requestLayout, 需要再重新测量、和布局。。 重复这个阶段
//如果在布局期间,重新发起了requestLayout, 需要再重新测量、和布局。。 重复这个阶段
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
false);
if (validLayoutRequesters != null) {
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// 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
// the second pass, which may result in posting those requests to the next
// frame instead
// frame instead
mHandlingLayoutInLayoutRequest = true;
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
" during layout: running second layout pass");
view.requestLayout();
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
....
我们再跟进DecorView#layout方法,由于DecorView是ViewGroup的间接子View所以,实现在ViewGroup的layout
@Override
@Override
public final void layout(int l, int t, int r, int b) {
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
if (mTransition != null) {
mTransition.layoutChange(this);
mTransition.layoutChange(this);
}
//最终还是调用到了View#layout
//最终还是调用到了View#layout
super.layout(l, t, r, b);
super.layout(l, t, r, b);
} else {
else {
// record the fact that we noop'd it; request layout when transition finishes
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
mLayoutCalledWhileSuppressed = true;
}
}
View#layout
public void layout(int l, int t, int r, int b) {
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { //测量发生了变化,需要重新测量,
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { //测量发生了变化,需要重新测量,
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldL = mLeft;
int oldT = mTop;
int oldT = mTop;
int oldB = mBottom;
int oldB = mBottom;
int oldR = mRight;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//布局发生变化,或者要去layout 。调用onLayout
//布局发生变化,或者要去layout 。调用onLayout
onLayout(changed, l, t, r, b);
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
else {
mRoundScrollbarRenderer = null;
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
从上面代码可以看出,在layout中,又调用了onLayout方法。同样,我们继续看看DecorView的onLayout方法
在DecorView#onLayout 中,调用的是super.onLayout. 其父类是FrameLayout。看看FrameLayout#onLayout
@Override
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//.... 依据子View的gravity计算子View的坐标。循环调用子View进行布局坐标计算
//.... 依据子View的gravity计算子View的坐标。循环调用子View进行布局坐标计算
child.layout(childLeft, childTop, childLeft + width, childTop + height);
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
好了,这里就完成了布局的坐标计算。我们再次回到ViewRootIml,分析下一个阶段 绘制 performOnDraw
ViewRootIml#performOnDraw
private void performDraw() {
private void performDraw() {
....
draw(fullRedrawNeeded);
draw(fullRedrawNeeded);
if (mReportNextDraw) {
if (mReportNextDraw) {
...
try {
try {
mWindowSession.finishDrawing(mWindow);
mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
catch (RemoteException e) {
}
}
}
可以看到,调用到了 draw(fullRedrawNeeded)方法,进行跟进
private void draw(boolean fullRedrawNeeded) {
private void draw(boolean fullRedrawNeeded) {
... 此处省略一大段源码,
此处省略一大段源码,
scrollToRectOrFocus(null, false);
scrollToRectOrFocus(null, false);
....
if (fullRedrawNeeded) {
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if(!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty){
if(!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty){
if(mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()){
if(mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()){
//支持硬件加速,硬件加速进行绘制
//支持硬件加速,硬件加速进行绘制
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
}else{
else{
....
//软件绘制
//软件绘制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
return;
}
}
}
好,我们继续跟进drawSoftware 方法:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
// Draw with software renderer.
final Canvas canvas;
final Canvas canvas;
try {
try {
final int left = dirty.left;
final int left = dirty.left;
final int top = dirty.top;
final int top = dirty.top;
final int right = dirty.right;
final int right = dirty.right;
final int bottom = dirty.bottom;
final int bottom = dirty.bottom;
//我们常见的canvas画布终于出现了
//我们常见的canvas画布终于出现了
canvas = mSurface.lockCanvas(dirty);
canvas = mSurface.lockCanvas(dirty);
.......
// If this bitmap's format includes an alpha channel, we
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// background. This automatically respects the clip/dirty region
// or
// or
// If we are applying an offset, we need to clear the area
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
dirty.setEmpty();
mIsAnimating = false;
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
try {
canvas.translate(-xoff, -yoff);
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
attachInfo.mSetIgnoreDirtyState = false;
//终于找到正主了!! , 看,进入到View的draw方法了!!
//终于找到正主了!! , 看,进入到View的draw方法了!!
mView.draw(canvas);
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
finally {
if (!attachInfo.mSetIgnoreDirtyState) {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
finally {
try {
try {
//绘制完成,解锁canvas
//绘制完成,解锁canvas
surface.unlockCanvasAndPost(canvas);
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
catch (IllegalArgumentException e) {
....
}
return true;
return true;
}
从上面代码,我们终于找到View#draw的调用了。
View#draw
@CallSuper
@CallSuper
public void draw(Canvas canvas) {
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
/*
* Draw traversal performs several drawing steps which must be executed
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
* in the appropriate order:
*
*
* 1. Draw the background
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 3. Draw view's content
* 4. Draw children
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
* 6. Draw decorations (scrollbars for instance)
*/
*/
// Step 1, draw the background, if needed
// Step 1, draw the background, if needed
int saveCount;
int saveCount;
if (!dirtyOpaque) {
if (!dirtyOpaque) {
drawBackground(canvas);
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
// Step 4, draw the children
dispatchDraw(canvas);
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
onDrawForeground(canvas);
// we're done...
// we're done...
return;
return;
}
注释写得比较清楚,一共分成6步,看到注释没有( // skip step 2 & 5 if possible (common case))除了2 和 5之外 我们一步一步来看:
第一步:背景绘制
主要是根据高宽和滚动位置,绘制背景区域
private void drawBackground(Canvas canvas) {
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
final Drawable background = mBackground;
if (background == null) {
if (background == null) {
return;
return;
}
setBackgroundBounds();
setBackgroundBounds();
...
final int scrollX = mScrollX;
final int scrollX = mScrollX;
final int scrollY = mScrollY;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
background.draw(canvas);
} else {
else {
canvas.translate(scrollX, scrollY);
canvas.translate(scrollX, scrollY);
background.draw(canvas);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
canvas.translate(-scrollX, -scrollY);
}
}
第二步、绘制边缘阴影 (可忽略)
第三步、绘制内容
<em>// Step 3, draw the content</em>
if (!dirtyOpaque) onDraw(canvas);
if (!dirtyOpaque) onDraw(canvas);
具体如何绘制,颜色线条什么样式就需要子View自己去实现,View.java 的onDraw(canvas) 是空实现,ViewGroup 也没有实现,每个View的内容是各不相同的,所以需要由子类去实现具体逻辑。
第四步、绘制子View
dispatchDraw(canvas) 方法是用来绘制子View的,View.java 的dispatchDraw()方法是一个空方法,因为View没有子View,不需要实现dispatchDraw ()方法,ViewGroup就不一样了,它实现了dispatchDraw ()方法:
ViewGroup#dispatchDraw
<span style="color:#ff0000">@Override</span>
protected void dispatchDraw(Canvas canvas) {
protected void dispatchDraw(Canvas canvas) {
...
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
for (int i = 0; i < count; i++) {
final View child = children[i];
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
else {
for (int i = 0; i < count; i++) {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
more |= drawChild(canvas, child, drawingTime);
}
}
}
......
}
遍历子View然后drawChild(),drawChild()方法实际调用的是子View.draw()方法,ViewGroup类已经为我们实现绘制子View的默认过程,这个实现基本能满足大部分需求,所以ViewGroup类的子类(LinearLayout,FrameLayout)也基本没有去重写dispatchDraw方法,我们在实现自定义控件,除非比较特别,不然一般也不需要去重写它, drawChild()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制了。
第五步、绘制边缘消失效果 可忽略
第六步、绘制滚动条, 其实就是绘制一个drawable
小结上诉的6步,整个draw的递归流程:
总结:一张图描述View的测量、布局、绘制过程。