控件的尺寸由其父控件传递过来的测量规格(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.EXACTLY | 1. 父控件为MeasureSpec.EXACTLY,同时子控件使用精确尺寸 2. 父控件为MeasureSpec.EXACTLY,同时子控件尺寸为LayoutParams.MATCH_PARENT 3. 父控件为MeasureSpec.AT_MOST,同时子控件使用精确尺寸 4. 父控件为MeasureSpec.UNSPECIFIED,并且子控件使用精确尺寸 |
MeasureSpec.AT_MOST | 1. 父控件为MeasureSpec.EXACTLY,同时子控件尺寸为LayoutParams.WRAP_CONTENT 2. 父控件为MeasureSpec.AT_MOST,同时子控件尺寸为LayoutParams.MATCH_PARENT 3. 父控件为LayoutParams.MATCH_PARENT,同时子控件尺寸为LayoutParams.WRAP_CONTENT |
MeasureSpec.UNSPECIFIED | 1. 父控件为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))
以上。