1 前言
MeasureSpec
翻译成中文就是测量规格,在 View
的测量过程中要使用到 MeasureSpec
。
2 组成
MeasureSpec
是由 32 位的 int
值来表示,高 2 位 表示测量模式(mode
),低 30 位 表示测量大小(size
),所以我们可以得出结论:测量规格(MeasureSpec
)= 测量模式(mode
) + 测量大小(size
),我们来看看 MeasureSpec
内部一些常量定义。
public static class MeasureSpec {
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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
MeasureSpec
是 View
的一个静态内部类,提供了 size
和 mode
打包和解包成 int
的操作,避免创建额外的变量,减少对象的内存分配。
2.1 测量模式
MeasureSpec
的测量模式 (mode
)有 3 种,都不同的含义。
UNSPECIFIED
EXACTLY
AT_MOST
模式 | 描述 |
---|---|
UNSPECIFIED | 父容器不对 View 进行限制,要多大就多大,一般用于系统内部,我们基本用不到 |
EXACTLY | 精确模式,父容器测量出 View 所需要的精确大小,View 最终大小就是 size 的大小,对应 LayoutParams 的 match_parent 和具体数值(10dp)这两种模式 |
AT_MOST | 父容器指定一个可用的大小,具体的值要看子元素实现,子元素不能大于这个值,对应 LayoutParams 的 wrap_content |
2.2 MeasureSpec 的计算
MeasureSpec
计算由两个因素决定,如下所示。
View
自身的LayoutParams
- 父容器的
MeasureSpec
另外对于顶级的 View
(DecorView
)和普通的 View
这两种 View
,计算 MeasureSpec
也有所不同,如下所示。
- 顶级
View
(DecorView
):自身的LayoutParams
和窗口的尺寸共同决定 - 普通
View
:View
自身的LayoutParams
和父容器的MeasureSpec
共同决定
3 源码分析
接下来我们看看 getChildMeasure()
分析如何计算出 MeasureSpec
的值
/**
*@param spec 父容器的测量值(MeasureSpec)
*@param padding view 当前尺寸的的内边距和外边距
*@param childDimension 子视图的布局参数(宽/高)
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父容器的测量模式
int specMode = MeasureSpec.getMode(spec);
//父容器的测量大小
int specSize = MeasureSpec.getSize(spec);
//通过父容器计算出子 view 大小,父容器剩余空间,但是不一定用这个值
int size = Math.max(0, specSize - padding);
//子 view 实际大小和模式
int resultSize = 0;
int resultMode = 0;
//判断父容器的模式
switch (specMode) {
//1.如果父容器测量模式是 EXACTLY
case MeasureSpec.EXACTLY:
//1.1.当子 view 有确切的值
if (childDimension >= 0) {
//子 view 大小为自身值,模式为 EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//1.2.子 view 的 LayoutParams 值为 MATCH_PARENT
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子 view 大小为父容器剩余空间,模式为 EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//1.3.子 view 的 LayoutParams 值为 WRAP_CONTENT
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子 view 大小不能超过父容器剩余空间,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//2.如果父容器测量模式是 AT_MOST
case MeasureSpec.AT_MOST:
//2.1.当子 view 有确切的值
if (childDimension >= 0) {
//子 view 大小为自身值,模式为 EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//2.2.子 view 的 LayoutParams 值为 MATCH_PARENT
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子 view 大小不能超过父容器剩余空间,模式为 AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
//2.3.子 view 的 LayoutParams 值为 WRAP_CONTENT
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子 view 大小不能超过父容器剩余空间,模式为 AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//3.如果父容器测量模式是 UNSPECIFIED
case MeasureSpec.UNSPECIFIED:
//2.1.当子 view 有确切的值
if (childDimension >= 0) {
//子 view 大小为自身值,模式为 EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//3.2.子 view 的 LayoutParams 值为 MATCH_PARENT
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子 view 大小 , 模式 UNSPECIFIED
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子 view 大小 , 模式 UNSPECIFIED
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
总结如下,这里我们不讨论父容器测量模式是 UNSPECIFIED
,一般用不到。
- 当子
View
为固定宽/高,不管父容器的MeasureSpec
是什么,子View
的测量模式都是为EXACTLY
,测量大小为自身大小 - 当子
View
宽/高为match_parent
,子View
的测量模式都是跟随父容器的测量模式,如果父容器测量模式为EXACTLY
,测量大小为父容器剩余空间,如果父容器测量模式为AT_MOST
,测量大小不能超过父容器剩余空间 - 当子
View
宽/高为wrap_content
,子View
的测量模式都是AT_MOST
,测量大小不能超过父容器剩余空间