之前分享了两个我们容易搞错的2个Android知识点,今天继续更新,我们还是要追求极致,把不懂的问题搞懂的~
这个知识点,我定义为在面试过程中答对不加分,答错扣分的题目,不过在我以前面试经历中,能完整说上来的同学不多。我们一起来看看大家对这个知识的掌握程度吧。在早期的博客的里面,很多时候,见到有如下的介绍:
-
如果你的 View 设置了 match_parent,则在onMeasure 中得到的测量模式为:EXACTLY;
-
如果设置了 wrap_conent,则对应测量模式为:AT_MOST;
-
还剩下一个 UNSPECIFIED大家不用管,不常用;
上述描述每句话都可以认为是错的。那么今天我要搞清楚几个问题:
- match_parent / wrap_conent一定对应 EXACTLY/ AT_MOST 吗 ?
- 测量模式到底是由哪些因素确定的?
- UNSPECIFIED 真的不常见吗?
1. match_parent和wrap_content就一定对应MeasureSpec.EXACTLY和MeasureSpec.AT_MOST吗?
肯定不是。
为什么呢?
因为View在measure时,它的宽高MeasureSpec完全是取决于父容器,父容器传的是什么它收到的就是什么。
如果这个父容器的onMeasure方法里面写死了每个子View的MeasureSpec的Mode为UNSPECIFIED的话,那么无论你在xml布局或者LayoutParams中怎么设置宽高都好,最终子View的onMeasure收到的也是UNSPECIFIED。
好吧,故意手动指定的不算。
就以正常的角度来看:
我们都知道,自定义ViewGroup过程中,需要在onMeasure里面对子View进行测量。
在测量子View时,往往会通过measureChild、measureChildWithMargins方法来完成(比如FrameLayout、LinearLayout、CoordinatorLayout、ViewPager2)。
或者调用ViewGroup的静态方法getChildMeasureSpec来直接获取目标子View的MeasureSpec,然后手动measure(比如ScrollView、NestedScrollView、DrawerLayout、TabLayout、ConstraintLayout)。
其实,measureChild和measureChildWithMargins里面也是会通过getChildMeasureSpec方法来获取MeasureSpec的,也就是说,上面提到的这些容器,在测量它们的子View之前,都是先通过getChildMeasureSpec方法来获取子View的宽高MeasureSpec,然后传给子View的measure方法的。
好,那我们现在来看看getChildMeasureSpec方法里面做了什么:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
......
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
......
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
......
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到:
在父容器的specMode为EXACTLY时,一切正常(子View尺寸指定为match_parent或精确的dimen值时,Mode = EXACTLY,尺寸指定为wrap_content则Mode = AT_MOST);
当父容器specMode为AT_MOST的时候,呵呵,可以看到,除了指定了dimen值之外,无论设置为match_parent或wrap_content,Mode最终都是会变成AT_MOST;
如果父容器specMode是UNSPECIFIED的话,跟上面的逻辑差不多,都是会变成UNSPECIFIED的,除非指定了精确的dimen值;
所以,View的onMeasure方法中收到的宽高MeasureSpec,不完全是由xml布局中设置的宽高或LayoutParams的宽高值决定的。
2. 有哪些因素影响着MeasureSpec的mode?
从刚刚的getChildMeasureSpec方法中可以看出,影响着View测量模式的因素主要是该View所属容器的测量模式。
也就是说,正常情况下(不是故意乱设置),View的测量模式是由:
它自身的LayoutParams设置的值+ 父容器的测量模式来决定的。
为什么大家都说MeasureSpec.UNSPECIFIED不常见呢?
MeasureSpec.UNSPECIFIED的相关知识:https://www.wanandroid.com/wenda/show/8613
大家都觉得这个模式不常见,很可能就是因为在编写布局时,View的宽高只能选择match_parent、wrap_content或者直接指定一个精确的尺寸,相对来说,MeasureSpec.UNSPECIFIED就显得不太透明了,因为在日常开发中,如不需定制View的话,基本上不会直接接触到。
3. MeasureSpec.UNSPECIFIED是不是真的不常见?
在日常定制View时,确实很少会专门针对这个模式去做特殊处理,大多数情况下,都会把它当成MeasureSpec.AT_MOST一样看待,就比如最最常用的TextView,它在测量时也是不会区分UNSPECIFIED和AT_MOST的。
不过,虽说这个模式比较少直接接触到,但很多场景下,我们已经在不知不觉中用上了,比如RecyclerView的Item,如果Item的宽/高是wrap_content且列表可滚动的话,那么Item的宽/高的测量模式就会是UNSPECIFIED。
还有就是NestedScrollView和ScrollView,因为它们都是扩展自FrameLayout,所以它们的子View会测量两次,第一次测量时,子View的heightMeasureSpec的模式是写死为UNSPECIFIED的。
我们在自定义ViewGroup过程中,如果允许子View的尺寸比ViewGroup大的话,在测量子View时就可以把Mode指定为UNSPECIFIED。
好了,希望这次你彻底弄明白了自定义控件的测量模式相关知识。
另外也有人给我发了个图,说这个图就能说明白了,其实这个图也有一点点小问题:
画圈的地方,这个值不一定是 0, 不过大多情况下 UNSPECIFIED这个模式一般不在乎这个 size。
最后
最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
上述【高清技术脑图】以及【配套的架构技术PDF】可以 关注我 【主页简介】 或者【简信】免费获取
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
网友评论