在自定义View和ViewGroup时,我们经常会遇到int型的MeasureSpec来表示一个组件的大小。这个变量里面不仅有组件的尺寸大小,还有大小的模式。这个大小的模式有点难以理解,但在系统中组件的大小模式有三种:精确模式(MeasureSpec.EXACTLY)、最大模式(MeasureSpec.AT_MOST)和未指定模式(MeasureSpec.UNSPECIFIED)。

int型整数可以表示两个东西:大小模式和大小的值。一个int类型我们知道有32位,而模式有三种,要表示三种状态,至少得2位二进制位。于是系统采用了最高的2位表示模式。如图所示:

- 最高两位是00的时候表示"未指定模式",即MeasureSpec.UNSPECIFIED。

- 最高两位是01的时候表示"精确模式",即MeasureSpec.EXACTLY。

- 最高两位是11的时候表示"最大模式",即MeasureSpec.AT_MOST。

为了操作简便,系统提供了一个MeasureSpec工具类。这个工具类有四个方法和三个常量供我们使用:

1. MeasureSpec.EXACTLY:使用measureSpec中size的值作为宽高的精确值。当我们将控件的layout_width或layout_height指定为具体数值时,如android:layout_width="50dip",或者为FILL_PARENT时,都是控件大小已经确定的情况,都是精确尺寸。

2. MeasureSpec.AT_MOST:使用measureSpec中size的值作为最大值,采用不超过这个值的最大允许值。当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。

MeasureSpec.UNSPECIFIED表示未指定尺寸,这种情况相对较少。以ScrollView嵌套ListView为例,我们需要重写onMeasure方法来实现正确的测量。

假设我们有一个高度为1000的列表项,其二进制表示为:1111101000。将其右移2位后得到:11111010,十进制为:250。这样就指定了ListView的高度为250px以内的最大允许值(一般就是250)。

如果MeasureSpec.EXACTLY被设置,那么ListView的高度将精确为250px。如果ListView的内容全部显示的高度为500px(大于250px),那么当measureSpec中的size值为250px(小于500px)时,效果是一样的。

如果MeasureSpec中的size值大于ListView内容全部显示的高度,那么设置成AT_MOST时,最多显示ListView内容全部显示的高度。而EXACTLY仍然显示MeasureSpec中的size值,所以在这种情况下,后面会留有空白高度(MeasureSpec中的size值大于ListView内容全部显示的高度的部分显示为空白)。

因此,通常这样写可以让ListView正确测量:MAX_VALUE右移2位后,即使不是最大整数了,ListView的高度也一般不可能超过它。第一个参数有个最大值的限制:1073741823(二进制的30个1),MAX_VALUE是1个0加上31个1(二进制),所以也可以右移1位,但是由于最前面两位表示mode,而不是size,所以右移1位和右移2位是一样的(前面两位的值都会被mode的代码覆盖)。