一、MeasureSpec简介
MeasureSpec是Android View系统中用于描述View在MeasureSpec上的要求,定义为一个32位的int类型变量。 32位的MeasureSpec中的高2位表示MeasureSpec的模式(Mode),剩下的30位表示MeasureSpec的大小(Size)。
二、MeasureSpec的三种模式
MeasureSpec的三种模式分别是UNSPECIFIED、EXACTLY和AT_MOST。 1、UNSPECIFIED模式(即为0)表示此View对尺寸没有任何参考意义,父View可以按照子View实际尺寸分配给它任意的空间。对于UNSPECIFIED模式的子View,虽然可以设置任意的宽高,但是其实意义并不是很大,对大部分View没有意义。 2、EXACTLY模式(即为MeasureSpec.EXACTLY,值为1073741824)表示View的确切大小已知,此时MeasureSpec中的size表示View的大小。对于EXACTLY模式的子View,想要占据特定的大小,需要确切的设定尺寸,否则将会导致View的不确定位置。 3、AT_MOST模式(即为MeasureSpec.AT_MOST,值为-2147483648)表示此View的尺寸是最大可获得的尺寸。对于AT_MOST模式的View,应该尽量适应于制约因素,尽量使得其不大于Specified Size的尺寸,否则就是不确定的,会影响其他控件的布局。
三、MeasureSpec的计算规则
MeasureSpec是通过父View的MeasureSpec和子View的LayoutParams来计算得到的。MeasureSpec的计算规则包括以下步骤: 1、获取父View的MeasureSpec以及父View的padding值(因为padding的值也是父View尺寸的一部分,所以需要将padding的值算入到父View的尺寸中)。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// 去除padding后的父View宽高
int parentWidth = widthSize - paddingLeft - paddingRight;
int parentHeight = heightSize - paddingTop - paddingBottom;
}
2、通过MeasureSpec.makeMeasureSpec方法,将父View的MeasureSpec和LayoutParams中的尺寸相结合,来计算子View的MeasureSpec。
public static int makeMeasureSpec(int size, int mode) {
if (mode == UNSPECIFIED) {
return size;
} else {
return size + mode;
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// 去除padding后的父View宽高
int parentWidth = widthSize - paddingLeft - paddingRight;
int parentHeight = heightSize - paddingTop - paddingBottom;
// 通过makeMeasureSpec方法,计算子View的宽度和高度MeasureSpec
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(200, MeasureSpec.AT_MOST);
}
3、通过measureChildWithMargins或measureChildren方法,测量出子View实际的宽度和高度。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// 去除padding后的父View宽高
int parentWidth = widthSize - paddingLeft - paddingRight;
int parentHeight = heightSize - paddingTop - paddingBottom;
// 通过makeMeasureSpec方法,计算子View的宽度和高度MeasureSpec
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(200, MeasureSpec.AT_MOST);
// 测量子View的宽高
measureChildren(childWidthMeasureSpec, childHeightMeasureSpec);
}
四、MeasureSpec的常见使用场景
MeasureSpec的常见使用场景包括:TableView的单元格高度、ListView/RecyclerView的Item高度、自定义View的测量等。 1、TableView的单元格高度
public void setCellWidthAndHeight(int width, int height) {
LayoutParams lp = new LayoutParams(width, height);
if (mTableLayout != null) {
TableRow tableRow = mTableLayout.findViewById(ROW_ID + mRowIndex);
View childView = tableRow.findViewById(COLUMN_ID + mColumnIndex);
if (childView != null) {
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
2、ListView/RecyclerView的Item高度
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null);
int parentWidth = parent.getMeasuredWidth();
int itemHeight = parentWidth / 2;
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);
itemView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
return new MyViewHolder(itemView);
}
3、自定义View的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// 去除padding后的父View宽高
int parentWidth = widthSize - paddingLeft - paddingRight;
int parentHeight = heightSize - paddingTop - paddingBottom;
// 计算子View的宽高MeasureSpec
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, LayoutParams.WRAP_CONTENT);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, LayoutParams.WRAP_CONTENT);
// 测量子View的宽高
measureChildren(childWidthMeasureSpec, childHeightMeasureSpec);
}