我们知道view的布局过程中会先测量自身的大小,其中调用view.onmeasure()
方法计算自身大小,并通过调用setMeasuredDimension
设置自身大小。如 view 的onmeasure
方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
复制代码
onmeasure
方法中包含两个参数:widthmeasurespec、heightmeasurespec,这两个参数是从父 view 中传递过来的,其作用是告知 view 如何测量自身大小。
一、MeasureSpec
拿 widthmeasurespec 来举例,widthmeasurespec 包含两个信息:specmode、specsize。 要了解 widthmeasurespec 是如何持有这两个信息的,可以看看下面这个函数:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
复制代码
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
复制代码
其中参数 measurespec 就是传递进来的 widthmeasurespec ,MODE_MASK 是一个常量:
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
复制代码
MODE_SHIFT = 30,也就是说 specmode 存放在 widthmeasurespec 的最高两位中,一共可以表示4个值;而 specsize 存放在后30位中。
现在再回过来看看 specmode、specsize 分别是什么意思。
二、SpecMode
上面说到 specmode 可以表示4个值,实际使用到的只有3个,它们的值定义在 View.MeasureSpec 中:
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
复制代码
要想了解这3个值代表的意思,我们来看看 view 中是怎么使用它们的。看看 view 的resolvesizeandstate
方法。
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
复制代码
其中参数 size 为自身期望的大小,参数 mesuredstate 暂不关心。
- 当 specmode 为 UNSPECIFIED 时,使用自身期望的大小;
- 当 specmode 为 EXACTLY 时,使用指定的确定的值(通常为layoutparams中确定的值);
- 当 specmode 为 AT_MOST 时,判断自身期望是否超过父 view 的限制,若未超过,则使用自身期望的值,若超过,则使用父view限制的最大值。
三、构造MeasureSpec
接下来我们再看看 measurespec 是如何构造的。
刚才我们讲到 widthmeasurespec 是从父 view 中向下传递的,那我们以较简单的 FrameLayout 为例来看看其 onmeasure 方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
// 保存最终的大小
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
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();
maxWidth = Math.max(maxWidth, // 保存子view中最大的宽度
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
...
}
}
// Account for padding too, 加上padding
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)); // 设置framelayout的大小
...
}
复制代码
该 onmeasure 方法主要完成了一个任务,找到子 view 的最大宽高,并以此为基础设置自身的大小。其中,我们最关心的是它如何完成子 view 的测量工作,也就是measureChildWithMargins
方法里做了些什么。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
该方法接收5个参数,分别是:需要测量的子 view、父 view 自身的 widthmeasurespec、已使用的宽度(可能为其他子 view 所用)、以及高度对应的2个参数。在这个函数里,我们就可以看到子 view 的 onmeasure 中所使用的 measurespec 是如何构造的,通过调用 getChildMeasureSpec 分别构造了用于测量子 view 的 widthmeasurespec、heightmeasurespec。 getChildMeasureSpec
接收3个参数,spec 为父 view 的 measurespec,padding 在这个例子中为父 view 的 padding + 子 view 的 margin + 已使用的大小,childdimension 为子 view 自身期望的大小。
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,父view的大小固定
case MeasureSpec.EXACTLY:
if (childDimension >= 0) { // 子view有一个确定的值
resultSize = childDimension; // 子view的大小为布局参数中指定的确定值
resultMode = MeasureSpec.EXACTLY; // 设置子view的specmode为EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) { // 子view希望填充父view
// Child wants to be our size. So be it.
resultSize = size; // 子view的大小由父view决定
resultMode = MeasureSpec.EXACTLY; // exactly
} else if (childDimension == LayoutParams.WRAP_CONTENT) { // 子view希望大小由自己决定
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; // 设置子view所能达到的最大值
resultMode = MeasureSpec.AT_MOST; //设置测量模式为at_most,表示子view的大小不能超过size中指定的值
}
break;
// Parent has imposed a maximum size on us, 父view的大小被限制
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) { // 跟随父view
// 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; // 父view被限制,子view受同样限制
} else if (childDimension == LayoutParams.WRAP_CONTENT) { // 子view自己决定
// 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不对子view作任何限制(这种情况常见于ListView中)
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);
}
复制代码
在这个方法中,我们可以看到,如果为子 view 设置了确定的大小,那么子 view 的 specmode 就时 EXACTLY;如果子 view 的大小是不确定的(即 MATCH_PARENT 或 WRAP_CONTENT),那么它的 specmode 就受父 view 的 specmode 影响。
确定了子 view 中 measurespec 的来源,那父 view 中的 measurespec 是谁设定的呢?这个问题可以到 ViewRootImpl 中寻找答案。
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;
}
复制代码