ViewGroup的几个方法

image.png

在 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 中收到的 widthMeasureSpecheightMeasureSpec
  • padding:父容器在该维度上已被占用的空间。
    通常包括父容器的内边距(padding)以及子视图可能占用的其他空间(如滚动条、分割线等)。
    计算公式:padding = getPaddingLeft() + getPaddingRight()getPaddingTop() + getPaddingBottom() 再额外加上其他预留空间。
  • childDimension:子视图在该维度上的期望尺寸,即子视图的 LayoutParams 中的 widthheight 值。
    可取具体数值、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)

使用示例

在自定义 ViewGrouponMeasure 中,为每个子视图生成 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 的所有子视图(只对 VISIBLEGONE ?实际上内部会跳过 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 需求时,直接调用此方法即可。
  • 如果需要对某些子视图进行特殊处理(比如某些子视图需要不同的测量策略),则需要手动遍历并调用 measureChildmeasureChildWithMargins

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 手动计算。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容