众所周知,Android的自定义View有三大方法,分别为测量、布局、绘制。

在两年多之前,我进行过绘制过程的大致梳理,然而就没有在研究过这个流程了。然后,直到有一天,一次面试过程中,被面试官问道:onMeasure(int widthMeasureSpec, int heightMeasureSpec)里,这个两个值应该如何去看?

我恍然大悟,我一直漏掉这一块(之前的Android自定义布局,这个方法几乎没有用到过)。尝试去了解,理解得云里雾里的。最近在博智林开发与机器人相关的App,于是乎,对于 | 与 & 这两个概念,以及负数在二进制中如何表示有了更深的理解。原来才知道,这个测量事件的两个值,是这样的理解。

Android的自定义View onMeasure方法,会带着两个参数,分别是widthMeasureSpec与heightMeasureSpec。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

那么,这个两个参数到底是什么含义呢?有人说是元数据,我回过头来看,确实理解了元数据这个概念,但是,如果你还没有理解,可以先忽略这个概念。

我把上面的方法,换一个写法,也可以正常的使用。

@Override
protected void onMeasure(int widthModel,int widthSize, int heightModel,int heightSize) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

我把widthMeasureSpec 分解成 int widthModel,int widthSize,把heightMeasureSpec分解成 int heightModel,int heightSize。

其中 widthModel 代表模式,也就是我们在xml文件中写的那些wrap_content,match_parent。而widthSize代表具体的大小。

这样一看,就很明白了。但是真实的场景,是一个参数代表两个意思。那么是如何做到的呢?

int 类型,在计算机里是32位的。一般的参数,一般不会全部用满32位,那么没有用满的数据,就用0表示。所以, 我们这里把widthMeasureSpec 看成是一个32位的数据,每一位上,可以是0,也可以是1。

Android把32位的从左到右的前两位,约定代表模式,也就是上述的 widthModel ,把32位的从左到右的后30位,约定代表大小,也就是上述的widthSize。还记得上面提到的元数据的概念吗?元数据就是还未分解的数据。

那么,如何分解?Android提供了一个类MeasureSpec,供我们使用。
获得模式的方法:

public static int getMode(int measureSpec) {
     //noinspection ResourceType
     return (measureSpec & MODE_MASK);
 }

获得大小的方法:

public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}

上述两个方法,都用到一个MODE_MASK的变量,这个变量是什么呢?

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

这个变量是0x3 向左移动 30位 的值。0x3的二进制是11,向左移动30位,变成一个11开头后面有30个0的数字。

这里再明确两个概念。

第一个概念:&。数学里的与。举个例子当1与0时,等于0。当1与1时,等于1。1在与任何数时,等于任何数。也可以说,0与任何数,都成0。&,是二进制层面的。

第二个概念:~。数学里的取反。举个例子0x3的二进制是11,向左移动30位,变成一个11开头后面有30个0的数字。然后再取反,就变成了00开头后面有30个1的数字。取反就是每一位上,0会变成1,1会变成0。

明确了上述概念后,我们再来看那两个方法。
获得模式的方法:

public static int getMode(int measureSpec) {
     //noinspection ResourceType
     return (measureSpec & MODE_MASK);
 }

首先,传进来一个元数据,与上MODE_MASK,而MODE_MASK上面我们已经说了,是一个开头是11后面跟着30个0的数字。在加上上面的第一个概念,其实这里是使元数据的后30位变成0。去掉后30位的数据,代表的就是模式。
这里得到的模式,有三种,分别是:
UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView
EXACTLY:确切的大小,如:100dp或者march_parent
AT_MOST:大小不可超过某数值,如:wrap_content

获得大小的方法:

public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}

这个方法,得到模式的方法,相差的是一个~的符号。根据上述的第二个概念,measureSpec与的数字是 前面00后面带30个1的数字。所以这里的操作其实就是在去掉前2位的数据,而去掉前两位的数据后,代表的就是大小。