在早期的博客的里面,很多时候,见到有如下的介绍:
- 如果你的 View 设置了 match_parent,则在onMeasure 中得到的测量模式为:EXACTLY;
- 如果设置了 wrap_conent,则对应测量模式为:AT_MOST;
- 还剩下一个 UNSPECIFIED大家不用管,不常用。
上述描述每句话都可以认为是错的。
那么今天的问题是:
- match_parent / wrap_conent一定对应 EXACTLY/ AT_MOST ?
- 测量模式到底是由哪些因素确定的?
- UNSPECIFIED 真的不常见吗?
更多问答 >>
-
每日一问 | Activity 启动动画对页面打开速度有影响吗?
2020-04-22 22:06 -
每日一问 | Fragment 是如何被存储与恢复的? 有更新
2020-06-07 09:01 -
每日一问 | 上周出现了大规模的github证书不可用的状态...但是真的是github服务器被攻击了么?
2020-04-01 21:49 -
每日一问 | 我们经常用的 String类型,你知道它最大可以放多长的字符串吗?
2020-04-08 23:58 -
2020-04-13 23:58
-
2020-03-23 23:45
-
每日一问 ViewPager 这个流传广泛的写法,其实是有问题的!
2020-03-26 00:10 -
每日一问 LifeCycle 对于 Lifecycle.Event 为啥不直接分发,而是通过 Lifecycle.State 中转?
2020-04-19 14:42 -
每日一问 今天考察下 Fragment 相关两个不常见 API
2020-03-19 00:55 -
每日一问 | 很久以前有Activity.onResume就是界面可见的说法,这种说法错了多少?
2020-03-15 23:10
肯定不是。为什么呢?因为View在match_parent
和wrap_content
就一定对应MeasureSpec.EXACTLY和MeasureSpec.AT_MOST吗?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
方法里面做了什么:可以看到:
在父容器的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的宽高值决定的。有那些因素影响着MeasureSpec的
从刚刚的mode
?getChildMeasureSpec
方法中可以看出,影响着View测量模式的因素主要是该View所属容器的测量模式。也就是说,正常情况下(不是故意乱设置),View的测量模式是由它自身的尺寸 + 父容器的测量模式来决定的。为什么大家都说MeasureSpec.UNSPECIFIED不常见呢?
MeasureSpec.UNSPECIFIED的相关知识:https://www.wanandroid.com/wenda/show/8613大家都觉得这个模式不常见,很可能就是因为在编写布局时,View的宽高只能选择match_parent
、wrap_content
或者直接指定一个精确的尺寸,相对来说,MeasureSpec.UNSPECIFIED就显得不太透明了,因为在日常开发中,如不需定制View的话,基本上不会直接接触到。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。
本来是准备看完了好好消化小缘发的回答,看到最后去搜《霍小玉传》了
这个 《霍小玉传》 太占焦点了。。。
哈哈哈
先说前两题,View的测量模式以及其尺寸,也就是View的MeasureSpec,其实是由View父容器MeasureSpec和View自身的LayoutParams决定。
相信大家都或多或少看过任主席的《开发艺术探索》,其中有一张著名的图至今被广大Blogger所借(C)用(V):经常写自定义ViewGroup的同学会发现,我们通常使用getChildMeasureSpec(int spec, int padding, int childDimension)方法来创建子View的MeasureSpec。这个方法就是进行了上图的流程,根据父View的SpecMode和子View的LayoutParams来得到一个size和一个mode。实际上真正生成子View测量规格的是MeasureSpec.makeMeasureSpec(int size,int mode)方法,该方法用public static修饰,也就是说父View完全可以绕过上图的流程,手动调用MeasureSpec.makeMeasureSpec()来自定义子View的MeasureSpec。
UNSPECIFIED 真的不常见吗?
常见又不常见。不常见是因为根据上图,除非父View是MesureSpec.UNSPECIFIED,我们的View才可能是UNSPECIFIED,然而除了DecorView,我们说的父View大部分都是我们自己指定的宽高,xml中从来都不能选择UNSPECIFIED。DecorView不可能指定子View为UNSPECIFIED,因此我们接触到UNSPECIFIED的情况少之又少。那为什么说常见呢?
实际上在我们平常使用的RecyclerView中,在item进行measure时,如果列表可滚动,并且item的宽或高设置了WRAP_CONTENT的话,那么接下来,itemView的onMeasure方法就会收到MesureSpec.UNSPECIFIED。还有就是 @九天zone同学 发的链接里的NestedScrollView嵌套RecyclerView的场景,可以看到在NestedScrollView的measureChild方法里:可以看到这里宽高的MeasureSpec使用的方法是不一样的,最终的测量模式及尺寸也就不一样,这就是上文说的手动调用MeasureSpec.makeMeasureSpec()来干扰子View的MeasureSpec创建。
有同学可能会问:那么为什么要让子View UNSPECIFIED呢?
View设置了wrap_content,按理来说其specMode应该是AT_MOST,也就是说其尺寸不应该超过父View。但对于一个可滚动ViewGroup来说,子View的宽高是可以超出父View的。举个栗子,对于一个水平滚动的RecyclerView来说,它自身的宽度一般是match_parent,也就是确定值,但是对于其item来说,宽度是可以无限大的。因为即使item的宽度超出了屏幕,RecyclerView只需要滚动就能将item完全显示出来,这种情况下,item想多宽就能有多宽,因此UNSPECIFIED显然比AT_MOST要更合适。这个图是书上的么,最后一列也可能不是 0.
跟书上是一样的,不确定后面勘误了没有
书上是以 Android5.0 的源码来分析的。
对于顶级 View 来说,测量模式和 match_parent,wrap_content 是一一对应的,查看这个方法:
对于普通 View 来说,测量模式和 match_parent,wrap_content 不是一一对应的,查看 getChildMeasureSpec 方法。
通常在一般单个控件的情况下,这两者是相对应的,但是如果对于ViewGroup组件而言就不一定了,以线性(垂直)布局为例,先measure子控件的大小,然后还需要计算是否有padding,那么它的实际大小就并不是view的大小,而且此时在onMearsure方法中获取获取到的数据就不一定准确了,它会优先MearsureChild子控件,然后再计算自身的线性布局控件大小,因此如果要想获取到准确的大小,那么就需要在onLayout中获取,此时的数据就是正确的
测量模式是和view自身空间以及父容器空间大小共同决定的
UNSPECIFIED是很常见的,比如match_parent,wrap_content
具体分析详见:<Android开发艺术探索>--View的工作原理
“通常在一般单个控件的情况下,这两者是相对应的”这句话你怎么得出的? 根据系统源码中的getChildMeasureSpec方法,无论是单个控件还是ViewGroup,都是由view自身Layout ...查看更多
“通常在一般单个控件的情况下,这两者是相对应的”这句话你怎么得出的? 根据系统源码中的getChildMeasureSpec方法,无论是单个控件还是ViewGroup,都是由view自身LayoutParams和父容器的MeasureSpec共同决定的;当然DecorView除外,它的MeasureSpec是和wrap_content,match_parent对应的。我说的这些结论均来自《Android开发艺术探索》。
111
有没有大佬具体解答 View.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
获取宽高的具体问题出在哪里实际上是由本身的模式和父类的模式共同得出的
EXACTLY/ AT_MOST/UNSPECIFIED都是属于SpecMode的。
EXACTLY:表示的模式是父容器已经测量出了View需要的精确大小(你已经看中了一个房子,他的大小已经确定了),它对应的是特定的数值与LayoutParams中的match_parent。(称为精确模式)AT_MOST:表示的模式是父容器已经指定了一个SpecSize,View的大小不能超过这个值(你还没出去看房子,根据你的预算,你确定了可以买多少平米内的房子)。它对应的是LayoutParams中的wrap_conent。(称为最大模式)前段时间的blog正好从一个现象着重讲了UNSPECIFIED,再推一遍~
https://juejin.im/post/5d75e8cd6fb9a06afd662bf3哇,昵称那尽然有个小红点,不过要直接点小红点,不能点昵称。先打个卡再说!!!📅