View 的测量 MeasureSpec
我们平常自定义都是重写 view 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法,而这个方法传入的宽和高都是一个32位 int 值。下面我们揭开这层神秘的面纱。
MeasureSpec 解析
MeasureSpec 是位于 View 类中的一个静态类,也就 100 行左右。我们先来看其中的变量。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 测量指定模式:父容器不对子 view 施加任何约束,它可以是它想要的任何尺寸。
* 一般系统中使用
* 二进制值为:00000000000000000000000000000000
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 测量规范模式:父容器确定子 view 的大小
* 相当于子 view 的match_parent
* 二进制值为:01000000000000000000000000000000
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 测量规范模式:父容器指定一个可用大小,子 view 的大小不能超过这个值。
* 相当于子 view 的 wrap_content
* 二进制值为:10000000000000000000000000000000
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
}
这三种模式都是通过对 MODE_SHIFT 进行左移得到的值,其二进制相应的值已经在注释中说明。所以说
View 的 MeasureSpec = SpecMode 模式(前两位) + SpecSize 尺寸(后30位)
MeasureSpec 方法
MeasureSpec 对外提供了一些静态方法,进行大小的打包和获取。
public static class MeasureSpec {
/**
* 将 size 大小和 mode 模式打包,生成一个 int 值
*/
public static int makeMeasureSpec(int size,nt mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* 获取 MeasureSpec 中的模式
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* 获取 MeasureSpec 中的尺寸
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
父 View如何通过 MeasureSpec 控制子 View 大小
在 ViewGroup 中有一个 measureChild
方法。可以看出,子 view 的 MeasureSpec 是通过父容器的 MeasureSpec 和子 view 的 LayoutParams所决定。最终会调用子 view 的 onMeasure
方法。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
MeasureSpec 和子 view 的 LayoutParams 对应关系如下图
子 View 的测量
至此,我们开始说的 onMeasure()
方法的两个参数来源都已经清楚了,再来看 View 中的 onMeasure()
执行了哪些操作。
/**
* 测量子 view 大小
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* 返回一个建议值
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
/**
* 根据建议大小和measureSpec返回默认大小
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}