View measure过程详解

我们知道view的布局过程中会先测量自身的大小,其中调用view.onmeasure()方法计算自身大小,并通过调用setMeasuredDimension设置自身大小。如 view 的onmeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
复制代码

onmeasure方法中包含两个参数:widthmeasurespecheightmeasurespec,这两个参数是从父 view 中传递过来的,其作用是告知 view 如何测量自身大小。

一、MeasureSpec

拿 widthmeasurespec 来举例,widthmeasurespec 包含两个信息:specmodespecsize。 要了解 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;
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值