深入理解MeasureSpec

发布时间:2023-05-21

一、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);
}