理解MeasureSpec widthMeasureSpec/heightMeasureSpec

在 onMeasure中的参数 heightMeasureSpec 又是什么?

MeasureSpec是父控件提供给子View的onMeaure参数,作为设定自身大小参考,只是个参考,要多大,还是View自己说了算。

MeasureSpec 是一个复合参数,包含了specMode和specSize,可以通过如下获取。

1
2
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

也可以通过如下合并

1
MeasureSpec.makeMeasureSpec(resultSize, resultMode);

specMode 有3种模式

  • UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView
  • EXACTLY:确切的大小,如:100dp或者march_parent
  • AT_MOST:大小不可超过某数值,如:wrap_content

子View的MeasureSpec由父View的MeasureSpec和子View本身的LayoutPramas共同决定,在ViewGroup的getChildMeasureSpec方法中实现,具体解析在下面代码中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

//spec parentWidthMeasureSpec
// childDimension 是子view 自己的维度
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取的父容器的 mode size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
//如果父容器 是 EXACTLY:确切的大小,如:100dp或者march\_parent
case MeasureSpec.EXACTLY:
//如果child view 有具体的大小。则直接就用这个大小。
// 例如child view 的xml里面描述的高度是 100dp
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;

//如果子child view的维度描述是 MATCH_PARENT
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//那么child view的size就是 等于 父容器的size
//mode 也等于父容器的mode
resultSize = size;
resultMode = MeasureSpec.EXACTLY;

//如果子child view的维度描述是 WRAP_CONTENT
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//那么child view的size也等于 父容器的size。但是mode是AT_MOST,child view可以参考这个size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// 如果父容器使用的是 AT\_MOST:大小不可超过某数值,如:wrap\_content
case MeasureSpec.AT_MOST:
//还是如果child view 指定了高度,都采用自己的高度,mode也是EXACTLY
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;

//如果子child view的维度描述是 MATCH_PARENT
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//child view 的参考size 等同于 父容器的size
//但此时父容器的size没有确定值,所以mode也同于AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//那么child view的size也等于 父容器的size。但是mode是AT_MOST,child view可以参考这个size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

//如果父容器使用的是 UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView
case MeasureSpec.UNSPECIFIED:
//还是如果child view 指定了高度,都采用自己的高度,mode也是EXACTLY
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子view MATCH_PARENT,则同于父size mode也是UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//WRAP_CONTENT
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}

//拼装Size 和Mode ,返回这个子view的MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
  • MeasureSpec不管父容器是什么szie和mode,如果child view 指定了高度,都采用自己的高度,mode也是EXACTLY
  • 当子View接收到父控件传递的MeasureSpec的时候,就可以知道父控件希望自己如何显示,这个点对于开发者而言就是onMeasure函数
  • ViewGroup在计算自己尺寸的时候,必须预先知道所有子View的尺寸
  • 如果父控件传递给的MeasureSpec的mode是MeasureSpec.UNSPECIFIED,就说明,父控件对自己没有任何限制,那么尺寸就选择自己需要的尺寸size
  • 如果父控件传递给的MeasureSpec的mode是MeasureSpec.EXACTLY,就说明父控件有明确的要求,希望自己能用measureSpec中的尺寸,这时就推荐使用MeasureSpec.getSize(measureSpec)
  • 如果父控件传递给的MeasureSpec的mode是MeasureSpec.AT_MOST,就说明父控件希望自己不要超出MeasureSpec.getSize(measureSpec),如果超出了,就选择MeasureSpec.getSize(measureSpec),否则用自己想要的尺寸就行了

ViewGroup的尺寸其实只需要三部:

  • 测量所有子View,获取所有子View的尺寸
  • 根据自身特点计算所需要的尺寸
  • 综合考量需要的尺寸跟父控件传递的MeasureSpec,得出一个合理的尺寸

顶层View的MeasureSpec是谁指定

传递给子View的MeasureSpec是父容器根据自己的MeasureSpec及子View的布局参数所确定的,那么根MeasureSpec是谁创建的呢?我们用最常用的两种Window来解释一下,Activity与Dialog,DecorView是Activity的根布局,传递给DecorView的MeasureSpec是系统根据Activity或者Dialog的Theme来确定的,也就是说,最初的MeasureSpec是直接根据Window的属性构建的,一般对于Activity来说,根MeasureSpec是EXACTLY+屏幕尺寸,对于Dialog来说,如果不做特殊设定会采用AT_MOST+屏幕尺寸。这里牵扯到WindowManagerService跟ActivityManagerService

感谢

1
2
3
4
5
6
链接:https://www.jianshu.com/p/cecd0de7ec27
来源:简书

作者:看书的小蜗牛
链接:https://www.jianshu.com/p/d16ec64181f2
来源:简书