简介
这篇文章继续就上一篇文章【View】- setContentView方法和UI绘制流程(源码分析)中performMeasure方法进行讲解,了解UI绘制的测量过程。
performMeasure
首先看一下传入的两个int型参数:childWidthMeasureSpec和childHeightMeasureSpec是怎么赋值的。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width)
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height)
看一下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;
}
rootDimension 是LayoutParams中的宽或者高,如果布局里面我们用的是
android:layout_width="match_parent"
那么rootDimension就等于ViewGroup.LayoutParams.MATCH_PARENT,如果是
android:layout_height="wrap_content"
那么rootDimension就等于ViewGroup.LayoutParams.WRAP_CONTENT。
如果布局指定了具体尺寸,那么就是默认的。
MeasureSpec.makeMeasureSpec方法将返回一个32为的整数,这个整数的前两位由传入的第二个参数的值组成,用来表示测量模式,整数的后30位由传入的第一个参数组成,用来表示View的宽高值。
performMeasure中调用View类中的measure(int widthMeasureSpec, int heightMeasureSpec)来实现功能。
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mView是DecorView实例,就是顶层布局。
measure
在方法中会执行onMeasure方法,开始测量布局。
...
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
...
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
...
}
...
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL);
mMeasureCache在调用forceLayout,requestLayout方法都会被清空。如果对应的key从缓存中取得了数据,那么直接高32为值赋值给宽,低32位赋值高。
onMeasure(widthMeasureSpec, heightMeasureSpec)
这里调用的是顶层DecorView的onMeasure方法。那么看一下作为容器onMeasure是怎么处理的,其它容器逻辑大致也会是这样。
调用DecorView父类的onMeasure方法,onMeasure继承于FrameLayout类,关于子组件的测量应该在父类里面。
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
在onMeasure中,得到DecorView子View个数,遍历每一个子View,将子View添加到mMatchParentChildren结合中。
判断当前布局的宽高是否是match_parent模式或者指定一个精确的大小,如果是则置measureMatchParent为false.
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY
遍历每一个子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中宽高的最大者,因为如果FrameLayout是wrap_content属性
//那么它的大小取决于子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());
//如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加
//宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
保存测量结果
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT))
遍历mMatchParentChildren集合,根据不同的测量模式计算出childWidthMeasureSpec, childHeightMeasureSpec,然后调用子View的measure(childWidthMeasureSpec, childHeightMeasureSpec)
// 子View中设置为match_parent的个数
count = mMatchParentChildren.size();
//只有FrameLayout的模式为wrap_content的时候才会执行下列语句
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//对FrameLayout的宽度规格设置,因为这会影响子View的测量
final int childWidthMeasureSpec;
/**
* 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:
* 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:
* 对于子Viw来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度
* 减去padding和margin后剩下的空间。
*
* 以下两点的结论,可以查看getChildMeasureSpec()方法:
*
* 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
* SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式
*
* 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
* SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式
*/
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
...
//对于这部分的子View需要重新进行measure过程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
接下来的流程和前面差不多,自己分析对应子View的测量流程。
如果父类onMeasure调用了两次,那么自己分析第一次调用处后面的代码。
重新测量
如果满足一定要求,会再次调用performMeasure方法,进行重新测量。
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
...
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
...
measureAgain = true;
}
if (measureAgain) {
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
网友评论