MeasureSpec 是父容器向子 View 传递尺寸约束的唯一载体:一个 32 位 int,高 2 位是测量模式,低 30 位是测量尺寸。
1. 为什么需要 MeasureSpec
Android 的 View 是\”父管子\”的树形结构:子 View 知道自己内容需要多大,父容器知道这块地盘还剩多大。两者信息不对称,就需要一份协商协议——只传一个 int size 无法区分\”硬性要求\”和\”上限\”,所以有了 MeasureSpec:尺寸 + 意图。
整条链路分四步:
① 父容器(比如 LinearLayout)
│ 用自己的 MeasureSpec + 子的 LayoutParams,算出子该遵守的 spec
▼
② 子 View.measure(childWidthSpec, childHeightSpec)
│ measure() 内部回调 onMeasure(),子在这里算出「我要多宽多高」
▼
③ 子 setMeasuredDimension(w, h)
│ 把算出的结果分别写进 mMeasuredWidth 和 mMeasuredHeight 两个字段
▼
④ 父容器读回这两个值,在 onLayout() 里摆放子的位置(测量阶段至此结束)
一句话概括:父出规则 → 子做计算 → 父收结果。规则来自「父的 MeasureSpec + 子的 LayoutParams」,计算发生在 onMeasure(),结果被父容器用在 onLayout() 里。
2. 二进制结构:一个 int 装两样东西
MeasureSpec 本质上是对一个 int 的封装(Java 里是 View 的静态内部类,Kotlin 里可以理解为一个 value class)。
31 30 | 29 ……………………………. 0
┌──────┬───────────────────────────────────────┐
│ Mode │ Size │
│ 2 bit│ 30 bit │
└──────┴───────────────────────────────────────┘
↑ 高 2 位存模式 ↑ 低 30 位存尺寸
核心常量:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT; // 0xC0000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 0x00000000
public static final int EXACTLY = 1 << MODE_SHIFT; // 0x40000000
public static final int AT_MOST = 2 << MODE_SHIFT; // 0x80000000
}
打包与解包:
// 打包:把 size 截断到 30 位,和 mode 按位或
public static int makeMeasureSpec(int size, int mode) {
// 保证 size 不会污染高 2 位的 mode
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); // 即 0x3FFFFFFF,最大约 1G
}
手动构造 spec 的常见写法:
int spec = View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY);
view.measure(spec, spec);
3. 三种测量模式
| EXACTLY | 1 << 30 | 你的尺寸已经定了,就是 size | 直接使用 specSize,不要再争辩 |
| AT_MOST | 2 << 30 | 你最大不能超过 size | 取「内容需要的大小」与 specSize 的较小值 |
| UNSPECIFIED | 0 | 我不管你,你自己随意 | 完全按内容需要返回(可滚动容器常用) |
对应到 XML 属性,直觉上:
| match_parent | EXACTLY(父可用空间) | AT_MOST(父可用空间) | UNSPECIFIED(0 或父可用空间) |
| wrap_content | AT_MOST(父可用空间) | AT_MOST(父可用空间) | UNSPECIFIED(0 或父可用空间) |
| 100dp | EXACTLY(100dp) | EXACTLY(100dp) | EXACTLY(100dp) |
可以看出:具体数值永远变成 EXACTLY,这是最稳定的一条规则。
典型的 UNSPECIFIED 场景:
- ScrollView 测量唯一子 View 的高度(只能滚一屏,所以高度不设上限)。
- ListView / RecyclerView 测量 item 的高度。
- 手动调用 view.measure(0, 0) 或 makeMeasureSpec(0, UNSPECIFIED) 做预测量。
- PopupWindow 在 WRAP_CONTENT 下测量内容。
4. 核心 API 速查
// —- 打包 / 解包 —-
View.MeasureSpec.makeMeasureSpec(int size, int mode) // 构造一个 measuicSpec
View.MeasureSpec.getMode(int measureSpec) // 取模式
View.MeasureSpec.getSize(int measureSpec) // 取尺寸
View.MeasureSpec.toString(int measureSpec) // 调试打印,如 \”EXACTLY 1080\”
// —- 父容器用它给子 View 生成 spec —-
ViewGroup.getChildMeasureSpec(int spec, int padding, int childDimension)
// —- 子 View 用它裁剪自己的测量结果 —-
View.getDefaultSize(int size, int measureSpec)
View.resolveSize(int size, int measureSpec) // 返回纯尺寸
View.resolveSizeAndState(int size, int ms, int childMeasuredState) // 带状态位
// —- 子 View 上报结果 —-
View.setMeasuredDimension(int measuredWidth, int measuredHeight)
View.getMeasuredWidth() / getMeasuredHeight()
View.getMeasuredState()
5. MeasureSpec 从哪来:测量规则的向下传递
5.1 树顶:DecorView / ViewRootImpl
测量从根节点开始。ViewRootImpl.performMeasure() 会拿到窗口可用尺寸,然后调用:
private static int getRootMeasureSpec(int windowSize, int measurement) {
int measureSpec;
switch (measurement) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 撑满窗口 → 硬性要求
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 最多撑满窗口 → 上限
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 具体数值 → 硬性要求
measureSpec = MeasureSpec.makeMeasureSpec(measurement, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
这就是\”根 MeasureSpec 的生成规则\”,也是整个映射表的第一行原型:MATCH_PARENT → EXACTLY、WRAP_CONTENT → AT_MOST、固定值 → EXACTLY。
5.2 中间层:ViewGroup.getChildMeasureSpec
每一个 ViewGroup 在测量自己的孩子时,都要把「自己的 spec」和「孩子的 LayoutParams」合成「孩子的 spec」。这段逻辑被抽取成了公共静态方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 可用空间 = 父给的尺寸 – 已占用的 padding / margin
int size = Math.max(0, specSize – padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父容器自己被强制固定大小
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 子 View 要具体数值,满足它
resultSize = childDimension;
resultMode = MeasureSpec.
网硕互联帮助中心





评论前必须登录!
注册