在上一篇绘制流程(一)中ViewRootImpl的performTraversals()方法会执行布局的测量、布局、绘制流程,接下来源码分析一下这些流程。
1.测量
ViewRootImpl的performTraversals()调用performMeasure()执行测量流程
ViewRootImpl.java
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
private void performTraversals(){
...
WindowManager.LayoutParams lp = mWindowAttributes;
...
//获取宽测量规格
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//获取高测量规格
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
//开始测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
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.
//大小为windowSize,测量模式为EXACTLY
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;
}
performMeasure方法接受两个参数:宽、高测量规格。这两个参数通过getRootMeasureSpec方法获取。在创建mWindowAtributes时,width和height被设置为MATCH_PARENT。
WindowManager.LayoutParams
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
因此childWidthMeasureSpec、childHeightMeasureSpec都为MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY)
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//调用DecorView的measure方法开始测量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
mView为DecorView它继承自FrameLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
//最终控件宽度所需大小
int maxHeight = 0;
//最终控件高度所需大小
int maxWidth = 0;
int childState = 0;
//开始遍历子View测量他们的大小
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//测量子View的大小
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//获取子View中所需最大宽度值
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
//获取子View中所需最大高度值
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//设置测量的规格
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}
这里调用measureChildWithMargins方法用来测量子View的大小
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//获取子View的测量规格
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);
//开始测量子View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
子View的测量规格由父容器测量规格以及自身的LayoutParams共同决定
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
// 父容器要求子View自己决定其大小
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);
}
| parentSpecMode | parentSpecSize | childDimension | childSpecMode | childSpecSize |
|---|---|---|---|---|
| EXACTLY | size | > 0 | EXACTLY | childDimension |
| 同上 | 同上 | MATCH_PARENT | EXACTLY | size |
| 同上 | 同上 | WRAP_CONTENT | AT_MOST | size |
| AT_MOST | 同上 | > 0 | EXACTLY | chidlDimension |
| 同上 | 同上 | MATCH_PARENT | AT_MOST | size |
| 同上 | 同上 | WRAP_CONTENT | AT_MOST | size |
| UNSPECIFIED | 同上 | >0 | EXACTLY | childDimension |
| 同上 | 同上 | MATCH_PARENT | UNSPECIFIED | size |
| 同上 | 同上 | WRAP_CONTENT | UNSPECIFIED | size |
ViewGroup的测量
ViewGroup需要测量所有子View,可调用
measureChild、measureChildren、measureChildWithMargins对子View进行测量。然后根据自身业务需求获取自身大小,通过setMeasuredDimension()方法来保存自身测量结果。
2.布局
ViewRootIml.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
if (host == null) {
return;
}
...
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
...
}
View.java
public void layout(int l, int t, int r, int b) {
...
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);
...
}
...
}
在setFrame方法中会设置View的left,top,right,bottom属性。对于ViewGroup需要在onLayout方法中对子View进行摆放(调用子View的layout方法)