在 View 的 onMeasure 回调中,有两个参数widthMeasureSpec
和heightMeasureSpec
,分别代表当前View的宽与高的MeasureSpec。
1. MeasureSpec 的本质
MeasureSpec 的本质是一个整型值。
众所周知,Java/Kt 中的整型占用了 32 位;而 MeasureSpec 的前2位表示测量模式(mode),后30位表示测量大小(size),实现了一个整型保存两个信息的目的。
通过MeasureSpec.getMode(measureSpec)
静态方法可以获取一个 MeasureSpec 的 mode,MeasureSpec.getSize(measureSpec)
可以获取到 size。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val wMode = MeasureSpec.getMode(widthMeasureSpec) // 水平方向的测量模式
val wSize = MeasureSpec.getSize(widthMeasureSpec) // 水平方向的测量大小
val hMode = MeasureSpec.getMode(heightMeasureSpec) // 竖直方向的测量模式
val hSize = MeasureSpec.getSize(heightMeasureSpec) // 竖直方向的测量大小
}
通过MeasureSpec.makeMeasureSpec(size, mode)
静态方法,可以将 mode 与 size 合成为一个 MeasureSpec。
size 不用多说,表示宽或高的长度,单位是像素。
mode 分为三种:MeasureSpec.UNSPECIFIED
、MeasureSpec.EXACTLY
、MeasureSpec.AT_MOST
;其中,MeasureSpec.UNSPECIFIED
基本不用,可以忽视。也就是说,常用的 MeasureSpec 只有EXACTLY
与AT_MOST
两种。
顾名思义,EXACTLY
即为精确模式,表示这个 ViewGroup 的宽或高是确定值size
;而AT_MOST
则是最大模式,表示这个 View 的宽或高最大能达到size
,真实长度根据实际情况而定。
在onMeasure
回调中,传入的 MeasureSpec 是根据当前 View 的父容器的 MeasureSpec 和当前 View 在布局中的layout_width
、layout_height
属性决定的。
2. MeasureSpec 的计算过程
那么,onMeasure
中的两个参数是如何得到的呢?
事实上,一般来讲,当一个 ViewGroup 测量自身的时候,会先遍历它的子 View,并调用子 View 的measure
方法进行测量,而measure
方法又会调用onMeasure
回调。
也就是说,View的onMeasure
回调方法中的两个参数是父容器计算并传递过来的。
下表反应了正常情况下父容器是如何得到一个子 View 的 MeasureSpec 的。其中横轴表示父容器的 MeasureSpec,纵轴表示了该 View 在布局文件中设置的宽或高,表中为该 View 的 MeasureSpec 的 mode 与 size。
子View在xml中 \ 父MeasureSpec | EXACTLY | AT_MOST |
---|---|---|
确定的数值 | mode: EXACTLY size: 布局中设置的大小 |
mode: EXACTLY size: 布局中设置的大小 |
wrap_content | mode: AT_MOST size: 父容器specSize |
mode: AT_MOST size: 父容器specSize |
match_parent | mode: EXACTLY size: 父容器specSize |
mode: AT_MOST size: 父容器specSize |
也就是说:
- 当布局中宽或高填写的固定值,那么对应的 mode 为 EXACTLY ,size 为填写的值;
- 当布局中的宽或高填写的
wrap_content
,那么对应的 mode 为 AT_MOST,size 为父容器的 size; - 当布局中的宽或高填写的
match_parent
,那么顾名思义,mode 和 size 都与父容器相同。
网友评论