自定义View时,控件测量模式为MeasureSpec.AT_MOST时如何确认其最终尺寸

控件的尺寸由其父控件传递过来的测量规格(MeasureSpec)和其自身在onMeasure中的测量逻辑来决定,而它的测量模式则是其父控件通过调用getChildMeasureSpec(int spec, int padding, int childDimension)方法来生成的,具体可以参看自定义ViewGroup时,如何确定子控件的MeasureSpec。简单的概括就是,当前控件的测量规格(MeasureSpec)由其父控件自己的测量规格(MeasureSpec)以及当前控件在xml或代码中设置的宽高来决定的,进而可以总结为,决定当前控件最终尺寸的因素一共有三个:父控件的测量规格(MeasureSpec)、自己使用时设置的宽高、自己onMeasure中的处理逻辑。 其中,父控件确定的测量规格(MeasureSpec)会通过onMeasure的参数的形式传递给子控件,子控件拿到后,在onMeasure中确定自己最终的尺寸。通常情况下,当子控件的模式为MeasureSpec.EXACTLY时,我们只需要从MeasureSpec中得到测量的尺寸,然后通过setMeasuredDimension(width, height)设置即可,而当子控件的测量模式为MeasureSpec.AT_MOST时,传递过来的尺寸为其所能使用的最大尺寸,而其实际要占用多少空间,则需要自己在onMeasure中确定。我在自定义ViewGroup时,如何确定子控件的MeasureSpec
中总结过,控件测量规格成立的各种条件,如下;

子控件MeasureSpec成立条件
MeasureSpec.EXACTLY1. 父控件为MeasureSpec.EXACTLY,同时子控件使用精确尺寸
2. 父控件为MeasureSpec.EXACTLY,同时子控件尺寸为LayoutParams.MATCH_PARENT
3. 父控件为MeasureSpec.AT_MOST,同时子控件使用精确尺寸
4. 父控件为MeasureSpec.UNSPECIFIED,并且子控件使用精确尺寸
MeasureSpec.AT_MOST1. 父控件为MeasureSpec.EXACTLY,同时子控件尺寸为LayoutParams.WRAP_CONTENT
2. 父控件为MeasureSpec.AT_MOST,同时子控件尺寸为LayoutParams.MATCH_PARENT
3. 父控件为LayoutParams.MATCH_PARENT,同时子控件尺寸为LayoutParams.WRAP_CONTENT
MeasureSpec.UNSPECIFIED1. 父控件为MeasureSpec.UNSPECIFIED, 并且同时子控件尺寸为LayoutParams.MATCH_PARENT
2. 父控件为MeasureSpec.UNSPECIFIED,并且同时子控件尺寸为LayoutParams.WRAP_CONTENT

为了明确各种模式下,当前控件的最终尺寸,我们就需要对MeasureSpec.AT_MOST的情况进行单独处理。在onMeasure中确定自己预期的尺寸,然后判断当前的测量模式是哪种,如果是MeasureSpec.EXACTLY,则直接使用父控件传递过来的测量规格(MeasureSpec)中的尺寸(size)即可,如果是MeasureSpec.AT_MOST,则需要自己计算,然后将尺寸设置为自己计算后的预期尺寸。View的源代码中提供了相应的方法,如下;

    /**
     * Version of {@link #resolveSizeAndState(int, int, int)}
     * returning only the {@link #MEASURED_SIZE_MASK} bits of the result.
     */
    public static int resolveSize(int size, int measureSpec) {
        return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
    }

当我们计算好自己的预期尺寸size后,将其和测量规格(meaureSpec)作为参数传递给resolveSize方法,其内容又调用了resolveSizeAndState方法,并且和MEASURED_SIZE_MASK做了与运算。其中MEASURED_SIZE_MASK的定义如下;

public static final int MEASURED_SIZE_MASK = 0x00ffffff;

除了高两位为0,其余部分为f,当和其他部分进行与运算时,高位的内容将被掩去,只保留后面的部分,这里这样做的原因是resolveSizeAndState方法的返回值包含了尺寸和状态,其中高两位保存状态,其他部分保存尺寸。但是由于我们只关心尺寸,所以在resolveSize中将状态部分掩去。接下来看一下resolveSizeAndState方法的具体实现,代码如下;

    /**
     * Utility to reconcile a desired size and state, with constraints imposed
     * by a MeasureSpec. Will take the desired size, unless a different size
     * is imposed by the constraints. The returned value is a compound integer,
     * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
     * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
     * resulting size is smaller than the size the view wants to be.
     *
     * @param size How big the view wants to be.
     * @param measureSpec Constraints imposed by the parent.
     * @param childMeasuredState Size information bit mask for the view's
     *                           children.
     * @return Size information bit mask as defined by
     *         {@link #MEASURED_SIZE_MASK} and
     *         {@link #MEASURED_STATE_TOO_SMALL}.
     */
    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    	// 0
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
        	// important!
            case MeasureSpec.AT_MOST: // 1
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY: // 2
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED: // 3
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK); // 4
    }

上面的代码中,

代码0
获取父控件传递的测量规格中的测量模式和测量尺寸

代码1
如果测量模式为MeasureSpec.AT_MOST,那么比较自己计算的预期尺寸和父控件传递过来的尺寸的大小,如果父控件传递的尺寸(specSize)小于我们自己测量的尺寸(size),那么就设置结果为specSize,并且和MEASURED_STATE_TOO_SMALL进行或运算,表示预期的尺寸超过了最大限制,只能使用最大尺寸,并且为结果设置了标志位,表示尺寸过大,其中MEASURED_STATE_TOO_SMALL定义如下;

public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;

如果我们自己测量的尺寸小于最大限制,那么直接使用我们自己的尺寸size即可。
代码2
如果为MeasureSpec.EXACTLY模式,则直接使用父控件传递的测量尺寸

代码3
如果为MeasureSpec.UNSPECIFIED模式,则使用我们自己测量的尺寸

代码4
结果和(childMeasuredState & MEASURED_STATE_MASK)做或运算,由于childMeasuredState 我们始终传递的0,所以后面的部分始终为0,对我们最终的结果没有影响。

通过resolveSize方法的调用,我们就确认了最终的尺寸,然后可以调用setMeasuredDimension方法设置最终的尺寸,如下;

setMeasuredDimension(resolveSize(width, measureWidth), resolveSize(height, measureHeight))

以上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旅行者40

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值