UI绘制流程的起始点 ViewRootImpl#performTraversals()方法中:
此方法里分别调用了:
///测量
performMeasure()
// 摆放布局
performLayout()
// 绘制
performDraw()
这也是我们自定义UI布局时注意的过程 : 测量(Measure)—>布局(Layout)—>绘制(Draw)
Measure测量过程:
1. 通过getRootMeasureSpec(int windowSize, int rootDimension)方法传入父容器windowSize(具体数值) rootDimension (LayoutParams.(width|height))获取子view宽高测量模式;
具体代码:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 窗口不能调整大小。强制根视图为windowSize
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 窗口可以调整大小。设置根视图的最大大小。
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 窗口想成为一个确切的大小。强制根视图是那个大小。
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
MeasureSpec:
在Measure流程中,系统将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,在onMeasure中根据这个MeasureSpec来确定view的测量宽高
测量模式:
- EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小------match_parent,精确值
- ATMOST : child view最终的大小不能超过父容器的给的------wrap_content
- UNSPECIFIED: 不确定,源码内部使用-------一般在ScrollView,ListView
MeasureSpec里通过和一个数值M size 模式 与或非运算 得到一个数值(测量模式值) 而后通过M进行一些运算可以拿到父容器 size 模式
2. 调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) 两个参数是根据父容器的宽高测量模式 ,在方法中 调用 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)又在这个方法里调用了onMeasure(int widthMeasureSpec, int heightMeasureSpec) 而 mView 为DecorView 而,DecorView继承FrameLayout ,onMeasure方法在FrameLayout被重写了,所以最终调用的是FrameLayout onMeasure方法;
3. FrameLayout onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:
容器view
1.获取子view数并遍历;
2.遍历过程 获取view child 判断 child.getVisibility() != GONE 时调用measureChildWithMargins()方法测量子view
遍历代码:
for (int i = 0; i < count; i++)
final View child = getChildAt(i);//获取子view
if (mMeasureAllChildren || child.getVisibility() != GONE) {//判断GONE 是GONE不用测量
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//测量child以及它的子view
final LayoutParams lp = (LayoutParams) child.getLayoutParams();//获取view LayoutParams
//获取所有子view中最大的宽或高
maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
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);
}
}
}
}
2.1.measureChildWithMargins()方法中传入了 child:子view, parentWidthMeasureSpec:父容器宽模式 parentHeightMeasureSpec:父容器高模式 等
2.2.measureChildWithMargins()具体执行为 代码:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
//获取子view MarginLayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根据子view宽或高对应的 父容器模式 Padding Margin width 获取该控件的测量模式
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 的子view 直到视图view为止
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
- 遍历完成之后获取了子view中最大宽 或 高 调用setMeasuredDimension()方法为该view设置宽高
setMeasuredDimension()后才可以获得view的宽高
小结:ViewGroup遍历测量Child三方法 自定义中使用:
-
measureChildWithMargins // 有Margin测量
-
measureChild// 测量这个view 没有Margin测量 自己遍历
-
measureChildren//遍历所有子view完成没有Margin测量
视图view
根据view设置内容 或呈现内容 来完成测量
setMeasuredDimension()调用完成测量
测量总结 自定义View,ViewGroup:
View:
- 套路:根据父容器传来的测量模式确定view宽高 以及自己内容 最终调用setMeasuredDimession方法来保存自己的测量宽高
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
break;
case MeasureSpec.AT_MOST:
break;
case MeasureSpec.EXACTLY:
//完成宽高测量
break;
}
setMeasuredDimension(width, height);
ViewGroup
- 1、测量子view的规格大小 measureChildWithMargins measureChild measureChildren等方法
- 2、通过子view的规格大小来确定自己的大小 setMeasuredDimession
Layout测量过程:
大概过程是Measure一样
ViewGroup
重写onLayout() 根据里要的样式计算每个view left, top, right, bottom 在调用子view layout(left, top, right, bottom)方法完成布局