MeasureSpec理解
简要
今天来聊聊MeasureSpec,记得刚接触的也感觉很难理解,知其然不知其所以然。MeasureSpec其实在面试中还经常会被问到,如果没有真正去理解它,不论是后续的开发或者面试中,难免会成为绊脚石。遂今天在此聊聊这个,可能以下文中理解有所偏颇。如果有什么不到之处或者看完还有不太理解的地方,可以在文章后面写下你的评论一起去探讨。
一说到MeasureSpec,大家本能反应是其是由高两位的mode和低30位的size组成的32位的一个int值,但要是再深入问一些可能回答不出来,例如:MeasureSpec为什么由mode+size的方式组成?MeasureSpec的值跟什么有关?或者父View与子View的MeasureSpec值的关系?自定义Viewd的onMeasure()方法要不要重写?你还在为这些面试中经常问的问题为难到吗?相信看完下文,心里多多少少都会有个答案。
组成
其实MeasureSpec是View的一个内部类,真正的身份就是帮助View完成测量功能。MeasureSpec类中最主要的部分由5个变量和3个方法组成,接下来根据原码中的代码慢慢一层层剖析。
变量
五个成员变量:
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;
方法
三个方法
public static int makeMeasureSpec( int size,int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
举例:
MeasureSpec运算.png位运算符号:
<< :左移位运算 符号左边是值,符号右边是左移的位数
& :与运算 相同位的值同为1则为,否为0
~ :优先级比算数运算符、关系运算符、逻辑运算符和其他运算符都高
解释
- MeasureSpec由mode+size的形式组合而成32位int值的好处是减少变量的创建
- MODE_MASK、UNSPECIFIED、EXACTLY 、AT_MOST 四个变量均左移30位,刚好高两位组成2*2排列组合,由位运算特性mode=MODE_MASK&mode
- makeMeasureSpec()方法目的就是通过mode和size组成MesureSpec的值,if条件判断SDK版本是否低于17,但是结果是相同的,只是高版本采用位运算的方式获取。
- getMode()方法是获取measureSpec的mode值,由于MODE_MASK低30位全是0,因此MODE_MASK&measureSpec计算的结果低30位为0,又因为measureSpec的高两位为mode值,又因为MODE_MASK的高两位全为1,因此MODE_MASK&measureSpec高两位的值为mode高两位的值,再加上低30位的0,因此MODE_MASK&measureSpec的结果就是mode值。
- getSize()方法是获取measureSpec的size值,由于~ 位运算优先级高于&运算,因此先~运算再&运算。~MODE_MASK运算之后高两位为0,低30位为1。~MODE_MASK&measureSpec的高两位只能为0,又因为measureSpec的低30位size值,因此~MODE_MASK&measureSpec的低30位为size值。
来源和决定因素
上面的组成部分讲解了MeasureSpec内部结构,其中mode与size参数的意义和如何获取。接下来说一说其如何帮助测量View的。我们都知道整个View体系是树状结构的,测量是由上至下的过程,View的测量功能由onMeasure()和measure()方法完成,其中onMeasure()属于回调方法,其结果再一层层回溯给顶层View。其中开发中我们只需要关注onMeasure()方法。onMeasure()方法中最重要的参数就是MeasureSpec值,接下来看一下自定义View的onMeasure()方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int specMode = MeasureSpec.getMode(widthMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
Log.e("TAG", "ChildView:MeasureSpec.EXACTLY");
break;
case MeasureSpec.AT_MOST:
Log.e("TAG", "ChildView:MeasureSpec.AT_MOST");
break;
case MeasureSpec.UNSPECIFIED:
Log.e("TAG", "ChildView:MeasureSpec.UNSPECIFIED");
break;
}
}
说明:
本文以下内容均只针对宽方向上来说
- 源头:widthMeasureSpec值是由其父类传递过来的
- 值 :widthMeasureSpec(自身)=super.widthMeasureSpec(父)+this.LayoutParams(自身)结果得来的
- LayoutParams是ViewGroup的静态内部类,由widthparam+widthparam组成,LayoutParams的类型是其父类的类型,值是自身的,常用的ViewGroup都重写,新增Gravity方位值。其值分为三类,MATCH_PARENT(-1)、WRAP_CONTENT(-2)、具体的dp值,后面具体的dp值用dp代替
上图说明:
1、上图是自定义View没重写onMeasure()的情况,谨记!!!
2、childView括号中的三个值从左到右为:LayoutParams的值、MeasureSpec的mode值(已经确定的)、测量过后的自身size值(此时称为measuredWidth)
3、正常情况下UNSPECIFIED模式用不到,所以自定义View的时候可以不做处理
4、getMeasuredWidth()≠getWidth(),getMeasuredWidth()在测量之后就有值了,但是getWidth()是没有值的
5、只有自身mode为EXACTLY时,测量的size值为自己设置的,mode为AT_MOST的时候size全是父类的值
总结
这里做一下总结,其中有两部分组成,第一部分是内部的组成关系,组成关系中最主要的是几个位运算和几个参数的意义。第二部分是值的来源和决定因素,onMeasure()方法中值由父类传递过来,其值是根据父类的mode和自身的LayoutParams决定。如果还感觉很模糊,不妨自己去写个例子去验证一下,这样可以加深印象,最重要的还是要理解。