view的测量过程
之所以先讲view的测量过程,是因为ViewGroup测量的时候是先把他的所有子view测量完成后才能测量viewgroup自身,view的measure是viewGroup来调用的。在view的测量过程中,parentView首先会调用getChildMeasureSpec(int spec, int padding, int childDimension)
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* 这个方法将根据父容器的MeasureSpec和子View LayoutParams中的宽/高
* 为子View生成最合适的MeasureSpec
*
* @param spec 父容器的MeasureSpec
* @param padding 父容器的内间距(padding)加上子View的外间距(margin)
* @param childDimension 子View的LayoutParams中封装的width/height
* @return 子View的MeasureSpec
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// ① 对父容器的MeasureSpec进行解包
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// ② 减去间距,得到最大可用空间
int size = Math.max(0, specSize - padding);
// 记录子View最终的大小和测量模式
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// ③ 父容器是精准测量模式
case MeasureSpec.EXACTLY:
if (childDimension >= 0) { //如果子view在LayoutParam中指定了大小,那么子view的resultSize 就是该大小,模式是EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子view中LayoutParams是MATCH_PARENT,则view的大小等于最大可用大小,测量模式是EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子view中LayoutParams是WRAP_CONTENT,则view的大小等于最大可用大小,测量模式是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// ④ 父容器指定了一个最大可用的空间
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// ⑤ 父容器不对子View的大小作出限制
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
// ⑥ 将最终的size和mode打包为子View需要的MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
计算该view的WidthMeasureSpec和HeightMeasureSpec,然后调用view的measure();
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
measure方法又调用了onMeasure方法,view.OnMeasure()才是真正完成了view的测量。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension( //设置测量的结果
getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec));
}
在view.onMeasure中调用setMeasuredDimension来保存测量的结果,那么测量结果是怎么来的呢?我们继续看getDefaultSize();
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//1、获得MeasureSpec的mode
int specMode = MeasureSpec.getMode(measureSpec);
//2、获得MeasureSpec的specSize
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
//这个我们先不看他
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
//3、可以看到,最终返回的size就是我们MeasureSpec中测量得到的size
result = specSize;
break;
}
return result;
}
在getDefaultSize中传入了两个参数,一个是最小大小,一个是MeasureSpec。首先获得MeasureSpec中的测量模式和暂定大小,然后根据测量模式来返回不同的测量结果。如果测量模式是UNSPECIFIED,则测量的结果就是最小的大小,如果测量模式是AT_MOST或者EXACTLY,测量的大小都是MeasureSpec中的大小。
而MeasureSpec中的大小是viewgroup调用getChildMeasureSpec生成的,查看getChildMeasureSpec的逻辑我们会看出,如果子view的layoutParem是warp_content,那么测量结果的测量模式就是AT_MOST,测量大小就是父容器的最大可用空间,所以我们在继承view自定义view的时候,如果重写onMeasure,那么设置LayoutParam设置warp_content是不能用的,大小始终是可用的最大大小。
那么getDefaultSize的最小大小是怎么来的呢?是通过getSuggestedMinimumWidth()和getSuggestedMinimumHeight()得到,我们继续看getSuggestedMinimumWidth。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
在getSuggestedMinimumWidth中,首先判断该view有没有背景,如果没有背景,返回android:minWidth(如果没有设置android:minWidth,就默认是0),如果有背景,就返回背景大小和android:minWidth中的最大值。
最后,onMeasure讲测量的结果调用
getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
保存起来,这样一个view就算测量完成了。
画个图来总结一下。
首先viewGroup会计算子view的MeasureSpec,然后讲所计算的MeasureSpec传入子view的measure中,子view开始测量自身。measure又会调用onMeasure来完成真正的测量过程。onMeasure会调用setMeasureDemension来保存测量的结果,测量结果是通过getDefaultSize来计算的,getDefaultSize通过比较最小的大小和MeasureSpec中的暂定大小,最终确定测量大小。
viewGroup的测量过程
viewGroup中,首先会测量所有子view的大小,然后根据子view的大小来确定viewGroup的大小。由于不同的ViewGroup的测量结果不一样(LinearLayout的height是所有view的height之和,而FrameLayout的Height是所有子view的最大的height),所以viewGroup中没有实现具体的onMeasure();我们以FrameLayout来举例。
首先还是measure()方法调用onMeaure方法,具体的测量实现我们看onMeasure()即可
//这里的widthMeasureSpec、heightMeasureSpec
//其实就是我们frameLayout可用的widthMeasureSpec 、
//heightMeasureSpec
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1、获得frameLayout下childView的个数
int count = getChildCount();
//2、看这里的代码我们可以根据前面的Measure图来进行分析,因为只要parent
//不是EXACTLY模式,以frameLayout为例,假设frameLayout本身还不是EXACTL模式,
// 那么表示他的大小此时还是不确定的,从表得知,此时frameLayout的大小是根据
//childView的最大值来设置的,这样就很好理解了,也就是childView测量好后还要再
//测量一次,因为此时frameLayout的值已经可以算出来了,对于child为MATCH_PARENT
//的,child的大小也就确定了,理解了这里,后面的代码就很 容易看懂了
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
//3、清理存储模式为MATCH_PARENT的child的队列
mMatchParentChildren.clear();
//4、下面三个值最终会用来设置frameLayout的大小
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//5、开始便利frameLayout下的所有child
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//6、小发现哦,只要mMeasureAllChildren是true,就算child是GONE也会被测量哦,
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//7、开始测量childView
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//8、下面代码是获取child中的width 和height的最大值,后面用来重新设置frameLayout,有需要的话
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
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());
//9、如果frameLayout不是EXACTLY,
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
//10、存储LayoutParams.MATCH_PARENT的child,因为现在还不知道frameLayout大小,
//也就无法设置child的大小,后面需重新测量
mMatchParentChildren.add(child);
}
}
}
}
....
//11、这里开始设置frameLayout的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//12、frameLayout大小确认了,我们就需要对宽或高为LayoutParams.MATCH_PARENTchild重新测量,设置大小
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
//13、注意这里,为child是EXACTLY类型的childWidthMeasureSpec,
//也就是大小已经测量出来了不需要再测量了
//通过MeasureSpec.makeMeasureSpec生成相应的MeasureSpec
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
//14、如果不是,说明此时的child的MeasureSpec是EXACTLY的,直接获取child的MeasureSpec,
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
// 这里是对高做处理,与宽类似
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
//最终,再次测量child
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 获取子视图的布局参数
final LayoutParams lp = child.getLayoutParams();
// 调用getChildMeasureSpec(),根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
// getChildMeasureSpec()请回看上面的解析
// 获取 ChildView 的 widthMeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
// 获取 ChildView 的 heightMeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
从上述代码我们可以将viewGroup的测量过程分为一下几个步骤
1.初始化变量
获取childCount,清理存储模式为MATCH_PARENT的child的队列,初始化最大宽度和最大高度。
2.遍历所有子view,获取子view的MeasureSpec,测量子view的大小,获得所有子view的最大宽度和最大高度
通过for循环遍历所有的子view,对于每一个子view,调用measureChildWithMargins,measureChildWithMargins中首先获得view的MeasureSpec,然后调用子view的measure完成子view的测量。
3. 根据自身MeasureSpec和子view的最大宽度和最大高度,确定自身大小
4. 重新确定LayoutParam==MATCH_PARENT的子view
由于LayoutParam==MATCH_PARENT的子view的大小还没有确定,根据已经确定的Viewgroup的大小,重新计算子view的精确大小。