现在的位置: 首页 > 综合 > 正文

【Android源码剖析】(API 19)[View—–>MeasureSpec]

2017年09月19日 ⁄ 综合 ⁄ 共 5171字 ⁄ 字号 评论关闭

MeasureSpec

MeasureSpec的全称是Measure Specification,意为“测量规格”。一个MeasureSpec对象封装了父布局传递给子布局的布局要求,每个MeasureSpec对象代表了一组宽度和高度要求。一个MeasureSpec对象由size和mode组成,MeasureSpec类通过将其封装在一个int值中以减少对象的分配。MeasureSpec的模式有三种:
UNSPECIFIED
父布局不对子元素做任何的约束,子元素可以得到任何想要的大小。
EXACTLY
子元素会以一个确定的值作为大小。
AT_MOST
子元素最多能是指定的大小。

Constants

private static final int MODE_SHIFT
该值指定了MeasureSpec三种模式切换时的进位大小,其默认值为30,意思是进位的大小为2的30次方。那么为什么会是30呢?我们知道一个int的长度大小为32位,而进位30位意思就是使用int的最高位和倒数第二位也就是32和31位作为标识位。
private static final int MODE_MASK
该值指定了MeasureSpec在进行位运算时的遮罩常量,其默认值为0x3 << MODE_SHIFT,也就是十六进制下的0x3左移30(MODE_SHIFT)位。十六进制下的0x3换位二进制即为11,左移30位即为:1100 0000 0000 0000 0000 0000 0000 0000。那这个遮罩的作用是什么?为什么要叫遮罩呢?其实其作用非常简单,只是用1标识需要的值而用0标识不需要的值,因为1与任何数做与运算都会得到该数而0与任何数做与运算都会得到0,就这么简单,这也就是为什么称其为遮罩,因为其能通过运算将不需要的值位“隐藏”起来而将需要的值位“显示”出来。
public static final int UNSPECIFIED
该值的意义开头有讲,这里就不重复累赘了。其默认值为0 << MODE_SHIFT,也就是十进制下的0左移30(MODE_SHIFT)位。十进制下的0换位二进制依然为0,左移30位也就是31个0,这里我们为了规范,将在左端补0凑够32位:0000 0000 0000 0000 0000 0000 0000 0000。
public static final int EXACTLY
该默认值为1 << MODE_SHIFT,也就是十进制下的1左移30(MODE_SHIFT)位。十进制下的1换位二进制依然为1,左移30位也就是1后跟30个0,这里我们为了规范,将在左端补0凑够32位:0100 0000 0000 0000 0000 0000 0000 0000。
public static final int AT_MOST
该默认值为2 << MODE_SHIFT,也就是十进制下的2左移30(MODE_SHIFT)位。十进制下的2换位二进制为10,左移30位也就是10后跟30个0:1000 0000 0000 0000 0000 0000 0000 0000。
MeasureSpec的三种模式就介绍到这里,大家有木有发现?这三种模式包括MODE_MASK的值在二进制下都只占两位,移位后刚好在最高位和倒数第二位也就是32和31位上!有木有感触?结合上面对MODE_MASK的介绍自己脑补下,下面我们来看看MeasureSpec类的方法

Methods

在介绍MeasureSpec类的方法之前我先要在这介绍一下View类的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,该方法有两个int型的参数值widthMeasureSpec和heightMeasureSpec,这两个值我们输出一下看看:

可以看到我们输出了两次相同的结果,也就是说onMeasure方法重复输出了两次,事实上该方法有可能会执行多次来对View进行测量,这具体得看View类的实现,我在讲View类的时候再细说,这里我们获取到onMeasure方法的两个参数值:
widthMeasureSpec = 1073742144(十进制) = 0100 0000 0000 0000 0000 0001 0100 0000(二进制)
heightMeasureSpec= 1073742260(十进制) = 0100 0000 0000 0000 0000 0001 1011 0100(二进制)
上面我们说到,MeasureSpec类用一个int值存储了size和mode两个属性,也就是说上面两个参数值均包含了size和mode两个属性在里面,那么系统又是如何提取和封装它们的呢?这里我先将其转换为二进制并补全其至32位便于我们计算,下面来看MeasureSpec的方法。
public static int getSize(int measureSpec)

顾名思义,该方法返回了MeasureSpec的size值,其具体实现如下:

return (measureSpec & ~MODE_MASK);

只是一个简单的位运算,我们来看看其具体计算过程。这里,我将上面的widthMeasureSpec 值传入该方法获取其返回值:

getSize的输出结果为320(十进制) = 0000 0000 0000 0000 0000 0001 0100 0000(二进制),那我们是如何得到这个值的呢?首先,取反MODE_MASK:
~MODE_MASK = ~1100 0000 0000 0000 0000 0000 0000 0000 = 0011 1111 1111 1111 1111 1111 1111 1111
再者,按位与:
0100 0000 0000 0000 0000 0001 0100 0000(widthMeasureSpec)
&
0011 1111 1111 1111 1111 1111 1111 1111(~MODE_MASK )
------------------------------------------------------------
0000 0000 0000 0000 0000 0001 0100 0000
转为十进制即为320,大家发现没有?Android巧妙地用Mask将size值从低位“提取”了出来!getSzie方法的原理就很清晰了:MODE_MASK取反后将32,31替换成0也就是去掉mode保留后30位的size。heightMeasureSpec的值也是一样的方法提取,下面我们再来看看与getSize类似的另一个方法
public static int getMode(int measureSpec)

该方法与getSize类似,是从MeasureSpec值中获取mode值,这里我在布局文件中设置该自定义控件的layout_width为match_parent:

<com.aigestudio.test.MyView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

同样我们将widthMeasureSpec传入该方法输出:

可以得到mode的值为1073741824(十进制) = 0100 0000 0000 0000 0000 0000 0000 0000(二进制),与上面的三类模式对比不难发现与EXACTLY的值一样,我们来看看该方法的具体实现:
return (measureSpec & MODE_MASK)

这里我就直接写过程了:

0100 0000 0000 0000 0000 0001 0100 0000(widthMeasureSpec)
&
1100 0000 0000 0000 0000 0000 0000 0000(MODE_MASK )
------------------------------------------------------------
0100 0000 0000 0000 0000 0000 0000 0000
看到木有!看到这里大家明白这个MODE_MASK的作用了么?将mode存储在32和31位中而将size存储在后30位中!这也是为什么MODE_SHIFT为30的原因!不难理解吧……
举一反三,下面我们将MyView的layout_width改为wrap_content看看:
<com.aigestudio.test.MyView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

输出:

得到mode的值为-2147483648(十进制) = 1000 0000 0000 0000 0000 0000 0000 0000(二进制),与上面的三类模式对比不难发现与AT_MOST的值一样,具体计算过程我就不写了。getMode方法的原理与上面的getSzie方法相反:用MODE_MASK后30位的0替换掉measureSpec后30位中的1再保留32和31位的mode值
既然可以从MeasureSpec值中“提取”size和mode那必然也有对应的方法将其“封装”至MeasureSpec值中,下面我们就来看看这个方法
public static int makeMeasureSpec(int size, int mode)
if (sUseBrokenMakeMeasureSpec) {
    return size + mode;
} else {
    return (size & ~MODE_MASK) | (mode & MODE_MASK);
}

该方法只有一个判断结构体,判断值sUseBrokenMakeMeasureSpec在View类中声明,追踪后发现其在View的构造函数View(Context context)中被赋值:

public View(Context context) {
	//……此处省略无关代码
    if (!sCompatibilityDone && context != null) {
        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;
        //……此处省略无关代码
    }
    //……此处省略无关代码
}

在构造函数中先做了一个判断,boolean类型的sCompatibilityDone在View类中声明并赋值为false,也就是说该判断结构体在View被实例化时总会被执行,在其中先获取了TargetSdkVersion也就是我们在AndroidManifest.xml中android:targetSdkVersion字段下设置的值,当应用的TargetSdkVersion高于JELLY_BEAN_MR1也就是高于4.2的时候makeMeasureSpec方法会直接返回size
+ mode的值否则将其做位运算后在返回。这里的设计原理就是getSize和getMode的逆运算:将mode的值存进32位二进制数的32和31位;将size的值存进32位二进制数的后30位,这样就能在上面的方法中分别提取size和mode值。

static int adjust(int measureSpec, int delta)

该方法是一个默认修饰符方法,仅供android.view包调用,View中不会直接调用上面的makeMeasureSpec方法但是会通过该方法的封装来间接地在measure方法中调用makeMeasureSpec方法,该方法会对View的布局长宽作调整,具体我在将View类的measure方法再说,其实现如下,其实看看计算过程你也能略知一二:

return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
public static String toString(int measureSpec)

这个方法不陌生撒,很好理解:

int mode = getMode(measureSpec);
int size = getSize(measureSpec);

StringBuilder sb = new StringBuilder("MeasureSpec: ");

if (mode == UNSPECIFIED)
    sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
    sb.append("EXACTLY ");
else if (mode == AT_MOST)
    sb.append("AT_MOST ");
else
    sb.append(mode).append(" ");

sb.append(size);
return sb.toString();

抱歉!评论已关闭.