
在 Android 自定义 ViewGroup 时,测量子视图的大小是核心工作之一。这四个方法都是 ViewGroup 中用于协助测量子视图的重要工具。下面逐一详细讲解。
1. getChildMeasureSpec(int spec, int padding, int childDimension)
这是一个静态工具方法,用于根据父容器的测量规格(MeasureSpec)、已占用的内边距/空隙、以及子视图期望的尺寸(layout_width / layout_height 的值),计算出子视图实际应该使用的 MeasureSpec。
方法签名
public static int getChildMeasureSpec(int spec, int padding, int childDimension)
参数含义
-
spec:父容器对该维度(宽或高)的 MeasureSpec(包含 mode 和 size)。
例如在onMeasure中收到的widthMeasureSpec或heightMeasureSpec。 -
padding:父容器在该维度上已被占用的空间。
通常包括父容器的内边距(padding)以及子视图可能占用的其他空间(如滚动条、分割线等)。
计算公式:padding = getPaddingLeft() + getPaddingRight()或getPaddingTop() + getPaddingBottom()再额外加上其他预留空间。 -
childDimension:子视图在该维度上的期望尺寸,即子视图的LayoutParams中的width或height值。
可取具体数值、MATCH_PARENT(-1)、WRAP_CONTENT(-2)。
返回值
返回一个 int 类型的 MeasureSpec,它封装了子视图应该使用的测量模式和大小。
这个结果可以直接作为子视图的 measure() 方法的参数。
工作原理
根据父容器的 MeasureSpec 和子视图的 LayoutParams,按照以下规则计算子视图的 MeasureSpec:
| 父容器 mode | 子视图尺寸 | 结果 mode | 结果 size |
|---|---|---|---|
| EXACTLY | 具体数值(>0) | EXACTLY | 子视图的数值(受父容器大小限制取最小值) |
| EXACTLY | MATCH_PARENT | EXACTLY | 父容器可用空间 = specSize - padding |
| EXACTLY | WRAP_CONTENT | AT_MOST | 父容器可用空间 |
| AT_MOST | 具体数值(>0) | EXACTLY | 子视图的数值(但不超过父容器可用空间) |
| AT_MOST | MATCH_PARENT | AT_MOST | 父容器可用空间 |
| AT_MOST | WRAP_CONTENT | AT_MOST | 父容器可用空间 |
| UNSPECIFIED | 具体数值(>0) | EXACTLY | 子视图的数值 |
| UNSPECIFIED | MATCH_PARENT | UNSPECIFIED | 0(无限制,通常为0) |
| UNSPECIFIED | WRAP_CONTENT | UNSPECIFIED | 0 |
可用空间 =
specSize - padding(且结果不会小于0)
使用示例
在自定义 ViewGroup 的 onMeasure 中,为每个子视图生成 MeasureSpec:
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
getPaddingLeft() + getPaddingRight(),
lp.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec,
getPaddingTop() + getPaddingBottom(),
lp.height);
childView.measure(childWidthSpec, childHeightSpec);
2. measureChild(View child, int parentWidthSpec, int parentHeightSpec)
这是一个便捷方法,内部调用了 getChildMeasureSpec,用于测量一个子视图。它考虑了父容器的 padding 和子视图的 LayoutParams,但不考虑子视图的 margin。
方法签名
public void measureChild(View child, int parentWidthSpec, int parentHeightSpec)
参数含义
-
child:要测量的子视图。 -
parentWidthSpec:父容器的宽度 MeasureSpec(从 onMeasure 传入)。 -
parentHeightSpec:父容器的高度 MeasureSpec(从 onMeasure 传入)。
内部逻辑(简化)
final LayoutParams lp = child.getLayoutParams();
final int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
getPaddingLeft() + getPaddingRight(), lp.width);
final int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
getPaddingTop() + getPaddingBottom(), lp.height);
child.measure(childWidthSpec, childHeightSpec);
特点
- 只使用父容器的 padding,忽略了子视图的 margin(因为 margin 是相对于父容器外部的,而测量时父容器已经知道自己的可用空间)。
- 如果你想考虑 margin,应该使用
measureChildWithMargins()。
使用场景
- 当你自定义的 ViewGroup 不需要处理子视图的 margin(比如 LinearLayout 默认不考虑 margin?其实 LinearLayout 会考虑,但 measureChild 是更底层的工具)。
- 一般在
onMeasure中循环遍历所有子视图并调用此方法。
3. measureChildren(int widthMeasureSpec, int heightMeasureSpec)
这是一个批量测量所有子视图的便利方法。它遍历当前 ViewGroup 的所有子视图(只对 VISIBLE 和 GONE ?实际上内部会跳过 GONE 吗?我们看源码:它会调用 measureChild,而 measureChild 内部没有判断 visibility,所以会测量所有子视图。但通常建议在自定义时手动跳过 GONE 的子视图以提升性能?实际上系统 ViewGroup 如 LinearLayout 在 onMeasure 中会判断可见性。不过 measureChildren 本身不筛选,它会测量每一个子视图。
方法签名
public void measureChildren(int widthMeasureSpec, int heightMeasureSpec)
参数含义
-
widthMeasureSpec:父容器的宽度 MeasureSpec。 -
heightMeasureSpec:父容器的高度 MeasureSpec。
内部逻辑
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 注意:源码中会跳过 GONE 的子视图
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
(实际上 ViewGroup 源码中有这个判断,只会对非 GONE 的子视图进行测量。)
使用场景
- 当你的 ViewGroup 所有子视图的测量方式一致(都遵循 padding + LayoutParams),且没有复杂的 margin 需求时,直接调用此方法即可。
- 如果需要对某些子视图进行特殊处理(比如某些子视图需要不同的测量策略),则需要手动遍历并调用
measureChild或measureChildWithMargins。
4. measureChildWithMargins(View child, int parentWidthSpec, int widthUsed, int parentHeightSpec, int heightUsed)
这是比 measureChild 更完整的一个方法,考虑了子视图的 margin 以及父容器中已经被其他子视图占用的空间(widthUsed / heightUsed)。常用于 LinearLayout、RelativeLayout 等需要处理 margin 并且可能分配剩余空间的布局。
方法签名
public void measureChildWithMargins(View child,
int parentWidthSpec, int widthUsed,
int parentHeightSpec, int heightUsed)
参数含义
-
child:要测量的子视图。 -
parentWidthSpec:父容器的宽度 MeasureSpec。 -
widthUsed:父容器中已经被占用的宽度(例如前面已测量的子视图所占的空间,或者装饰元素占用的空间)。实际计算子视图可用宽度时,会从父容器总宽度中减去这部分。 -
parentHeightSpec:父容器的高度 MeasureSpec。 -
heightUsed:父容器中已经被占用的高度。
内部逻辑(简化)
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed,
lp.width);
final int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed,
lp.height);
child.measure(childWidthSpec, childHeightSpec);
关键点
- 它强制要求子视图的 LayoutParams 是
MarginLayoutParams或其子类(如ViewGroup.MarginLayoutParams),否则会抛出ClassCastException。 - 这里的
widthUsed/heightUsed是一个累加值,用于实现按权重分配剩余空间的场景。例如 LinearLayout 在测量第一个子视图后,会更新widthUsed,再测量第二个子视图。 - 与
measureChild相比,它额外减去了子视图的 margin 以及used参数。
使用场景
- 需要支持子视图 margin 的 ViewGroup(如 LinearLayout、RelativeLayout)。
- 需要按顺序分配剩余空间(例如线性布局中,先测量 WRAP_CONTENT 的子视图,再根据剩余空间测量 MATCH_PARENT 或 weight 子视图)。
总结对比
| 方法 | 是否考虑子 view margin | 是否支持 used 空间 | 批量操作 | 备注 |
|---|---|---|---|---|
getChildMeasureSpec |
不直接考虑,需手动传入 | 手动传入 padding+占用 | 否 | 底层工具,返回 MeasureSpec |
measureChild |
否(只考虑 padding) | 否 | 单个 | 简单场景 |
measureChildren |
否(内部调 measureChild) | 否 | 全部子 view | 会跳过 GONE 子视图 |
measureChildWithMargins |
是 | 是(通过 used 参数) | 单个 | 复杂布局(LinearLayout 等)核心方法 |
在实际开发中:
- 最简单的自定义 ViewGroup(例如流式布局不需要 margin 支持)可以用
measureChildren。 - 如果需要支持 margin,一般会重写
onMeasure并手动调用measureChildWithMargins,同时维护used变量。 - 如果完全自定义复杂的测量逻辑,可能会直接调用
getChildMeasureSpec手动计算。