我们自定义控件的时候,除了需要复写onDraw()方法实现我们view的视图外,有时候还需要复写onMeasure()来进行对自定义控件的大小进行精确的计算,以便自定义控件能更好的显示其内容。
onMeasure(int widthMeasureSpec, int heightMeasureSpec)中的参数,是父控件传进来的对子控件大小进行说明和限制的信息,包含了size(大小)和mode(模式)的两个信息。google工程师为了不另外开辟对象空间来存储这两个信息,于是android提供了MeasureSpec这个类来打包size(大小)和mode(模式)为int值,并提供了解包的方法供提取出size(大小)和mode(模式)这两个信息。
API17以及之前,这个类提供的打包方法非常简单,但是存在bug。之后的版本已经修复了这个问题。(看代码,你能发现这个bug么)
/
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
很容易看出这方法的参数 ,如果对换位置并不影响返回的结果。并且如果传入的其中一个值溢出,会导致测量结果的不准确。RelativeLayout就受到这个bug的影响。之后的版本,修复后的代码如下:
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
方法是做了兼容,在API17版本之前还继续使用原来的实现方法。sUseBrokenMakeMeasureSpec代表当前API是否在17以及之前。 下面我们来看看完整的MeasureSpec类是如何进行打包和解包的。(这里只保留了关键代码,并做了注释)。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 该模式下,代表父控件并没有对子控件大小做任何限制,子控件可以想多大就多大。
*/
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;
//打包大小和模式,传入的mode必须是上面定义好的三种模式之一
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//解包获取模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//解包获取大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
MODE_MASK:
11000000000000000000000000000000
UNSPECIFIED:
00000000000000000000000000000000
EXACTLY:
01000000000000000000000000000000
AT_MOST:
10000000000000000000000000000000
~MODE_MASK:
00111111111111111111111111111111
这里我们假设一下,当size等于50,mode为AT_MOST的时候,我们进行计算一下,首先是size部分:size&~MODE_MASK :
00000000000000000000000100010010
&
00111111111111111111111111111111
=
00000000000000000000000100010010
实际上没有变对吧。同样的,来看看mode部分:mode & MODE_MASK :
10000000000000000000000000000000
&
11000000000000000000000000000000
=
10000000000000000000000000000000
运算结果和模式原来的一样,然后整个计算就是很巧妙的,模式数据在前面,大小数据在后面,(size & ~MODE_MASK) | (mode & MODE_MASK)
00000000000000000000000100010010
|
10000000000000000000000000000000
=
10000000000000000000000100010010
上面的运算结果就包含了模式和大小的数据,模式数据存在头部,大小则存在尾部,取出的时候 ,我们看看如何。先看mode的取出方法: measureSpec & MODE_MASK:
10000000000000000000000100010010
&
11000000000000000000000000000000
=
10000000000000000000000000000000
然后取出size的运算:measureSpec & ~MODE_MASK
10000000000000000000000100010010
&
00111111111111111111111111111111
=
00000000000000000000000100010010
就这样,非常简单的使用逻辑运算进行打包和解包。现在回到打包的时候,打包的时候size部分的运算和mode部分的运算其实都没有变化(真的没有变化么),也就是说,makeMeasureSpec(int size, int mode)方法实际上可以简单点(少做点运算):
public static int makeMeasureSpec(int size, int mode) {
return size|mode;
// return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
注释掉的一行是谷歌工程师写的,至于为么这么写,我想应该是为了方便看出和取出方法相对应吧,你觉得呢?哈哈,当然不是了,如果按照简单的做法和API17以及之前的做法是一样的。试想一下,假如传入的size是负值,最高位符号位为1,运算后会影响到之后的size和mode取值的正确性。大家可以自己运算一下。