云计算百科
云计算领域专业知识百科平台

Android MeasureSpec 详解

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. 三种测量模式

模式
常量
父容器的含义
子 View 应该怎么做
EXACTLY 1 << 30 你的尺寸已经定了,就是 size 直接使用 specSize,不要再争辩
AT_MOST 2 << 30 你最大不能超过 size 取「内容需要的大小」与 specSize 的较小值
UNSPECIFIED 0 我不管你,你自己随意 完全按内容需要返回(可滚动容器常用)

对应到 XML 属性,直觉上:

LayoutParams
父为 EXACTLY
父为 AT_MOST
父为 UNSPECIFIED
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.

赞(0)
未经允许不得转载:网硕互联帮助中心 » Android MeasureSpec 详解
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!