本篇我们将讨论MeasureSpec类,搞清楚size和mode是怎样储存以及怎样提取的。
我们来看FrameLayout.java的第二句
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
这里涉及到MeasureSpec类,这个类是View.java里的内部静态类,成员变量如下
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(UNSPECIFIED, EXACTLY, AT_MOST),然后还定义了MODE_SHIFT和MODE_MASK,我们来看看具体MASK的作用和过程是什么。来看makeMeasureSpec这个函数
public makeMeasureSpec(size, mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
第一部分size + mode是在旧版本上的处理方式,有bug(没考证),所以出现了新版本的处理方式。用了bit-wise mask,我们来详细看看这个mask的原理
bit-mask原理
有两个数字(二进制),a, b,我想要得到一个新数字c,其中c的某2,3位由b决定,剩余的位由a决定,比如:
a = 10 10 10 // 已知
b = 01 01 11 // 已知
c = 10 01 10 // 要求,注意c的2, 3位来自于b, 其余来自于a
如何做到呢?我们先定义一个mask,因为要2, 3位,所以这两位为1,其余为0.
m = 00 11 00 // mask
做如下操作就可以得到c
(a & ~m) | (a & m)
分解一下,先看第一部分
a & ~m = 10 10 10
& 11 00 11 // &~m的意思是,去掉2,3位,其余都留下
----------------
10 00 10 // a除了2,3位,其余被保留下来了
再看第二部分
b & m = 01 01 11
& 00 11 00 // &m的意思是,只要2,3位,其余都丢掉
----------------
00 01 00 // b只有2,3位被保留下来了
然后合起来
(a & ~m) | (b & m) = 10 00 10
| 00 01 00 // |的意思是,加在一起,因为不同位被mask掉了,所以不用考虑进位问题
----------------
10 01 10 // 和上面要求的c的值一样
将size和mode进行bit-mask
framework从效率考虑,不想让MeasureSpec类建立对象,所以通过bit-mask的方法将size和mode储存为一个唯一的int,然后要用的时候再反算回来。我们看下是怎么反算回size和mode的。
int getMode(int measureSpec) {return (measureSpec & MODE_MASK);}
int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);}
不过通过这样的操作真的可以把原本的数字反算回来吗?不行的,如果不相信可以用上面的a,b,c,m的例子算一下。是无法从c和m算回a和b的。
framework这样算之所以是可行的和MASK_SHIFT有关
private static final int MODE_SHIFT = 30;
mode被移动了30位....相当之大。以下括号里位30位及以内的位数
private static final int MODE_MASK = 0x3 << MODE_SHIFT; // 11 (000...)
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00 (000...)
public static final int EXACTLY = 1 << MODE_SHIFT; // 01 (000...)
public static final int AT_MOST = 2 << MODE_SHIFT; // 10 (000...)
而size是没有位移的,size被转为二进制之后,根本无法填满30位这么多,所以
(size & ~MODE_MASK) | (mode & MODE_MASK);
的含义是,让mode决定30以上的位数,让size决定30位以下的位数,他们不会冲突,因为size转为二进制后无法达到30位以上(谁这么丧心病狂用这么大的屏幕)。
我们来做一个简化的操作
MODE_MASK = 11 00 00 // framework设定
mode = 01 00 00 // framework设定, EXACTLY
size = 00 00 01 // 假设的数字
那么
size & ~MODE_MASK = 00 00 01
& 00 11 11
--------------
00 00 01
然后
mode & MODE_MASK = 01 00 00
& 11 00 00
--------------
01 00 00
最后
measureSpec = (size & ~MODE_MASK) | (mode & MODE_MASK)
= 00 00 01 // 30位及以下由size决定
| 01 00 00 // 30位以上由mode决定
-------------
01 00 01 // 因为size很小,所以没有冲突,这里算假设很大了
然后反过来算mode
measureSpec & MODE_MASK = 01 00 01
& 11 00 00 // 30位及以下的值被mask去掉了
-----------
01 00 00 // mode的值出来了
算mode
measureSpec & ~MODE_MASK = 01 00 01
& 00 11 11 // 30位以上的值被~mask去掉了
-----------
00 00 01 // size的值出来了
可以看到,之所以能算出来还是因为位移后的mode和size大小差距太大,所以互不影响。妙啊!!都是什么神人想出来的。
剩下的两个函数比较直接
static int adjust(int measureSpec, int delta) // 把measureSpec直接加上delta大小,因为mode太大,所以只影响到了size,相当于调整了size
public static String toString(int measureSpec) // 把mode数字转为UNSPECIFIED, EXACTLY或AT_MOST,size直接显示出来
接下来我们来看看mode的三种情况的含义,怎样确定使用哪种mode,和xml中设定的wrap_content, match_parent的关系又是怎样。
----------------------------------------------------------------------------
FrameLayout measure过程源码Log全解析系列