1. 前言:从 Compose 生命周期说起

一文看懂 Jetapck Compose 布局流程_子节点

Compose 绘制生命周期为三个阶段:

  1. Composition/组合:Composable 源码经过运行后生成 LayoutNode 的节点树,这棵树被称为 Composition。
  2. Layout/布局:对节点树深度遍历测量子节点的尺寸,并将其在父容器内摆放到合适的位置。
  3. Drawing/绘制:基于布局后拿到的尺寸和位置信息,绘制上屏。

我们与 Android 经典视图系统的生命周期(Measure,Layout,Drawing)做一个对比:组合是 Compose 的特有阶段,是其能够通过函数调用实现声明式 UI 的核心,想要深入理解 Compose 第一课就是理解这个过程。但我已经有一些文章介绍过了,不在本文讨论范围。可以参考 深入理解 Jetpack Compose 内核:SlotTable 系统

绘制阶段与传统视图大同小异,都是通过 Android Cavas API ,底层调用 skia 实现,本文也不做讨论。

本文讨论的重点是布局阶段。Compose 的布局把 Measure 也了囊括进来,相对于 Android View 有相似性,但也有其独有的特点和优势,接下来我们进入正题。

2. Compose 布局过程三步走

Compose 布局包括三个阶段,从当前 Node 出发,需要依次经历:

  1. Measure children: 深度遍历子节点,并测量它们的尺寸。
  2. Decide own size:根据收集到的子节点尺寸,决定当前节点自己的尺寸
  3. Place children:将子节点摆放到合理的相对位置

一文看懂 Jetapck Compose 布局流程_Image_02

上面代码描述了一个卡片的布局,下面以这个布局的节点树为例,看一下布局流程

一文看懂 Jetapck Compose 布局流程_Android Jetpack_03

  • Step1:从 Row 开始发起测量,遵循三步走第一步,深度遍历测量其子节点 ImageColumn
  • Step2&3Image发起测量,因为没有子节点需要测量了,所以只需要计算自己的尺寸,也因为没有子节点需要摆放,空实现完成 place 即可。
  • Step4Column 发起测量,因其有子节点,继续深度遍历
  • Step5&6:测量 Text ,因为一个叶子节点,立即完成自己的 Size 和 Place 阶段
  • Step7&8:测量另一个 Text,同上
  • Step9:Column 拿到两个子 Text 返回的 Size 后,计算出自己的 Size,不难猜到其计算逻辑应该是 width = maxOf(child1.w, child2.w), height = sumOf(child1.h, child2.h)。 设置自己的 width 和 height 后,对两个子 Text 进行 Place, 垂直线性摆放。

看一下代码是如何实现这三步。

所有的 Composable 最终都会调用一个公共 Layout Composable 方法,这里面创建 LayoutNode 存储在 Composition 节点树

一文看懂 Jetapck Compose 布局流程_客户端_04

Column 的实现为例,可以看到调用 Layout 时,传入了三个参数

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • content:在这里定义子 Composable ,组合过后形成当前节点的子节点
  • measurePolicy:这是定义了布局的三步走核心逻辑
  • modifier:修饰符链,参与到布局或者绘制阶段

measurePolicymodifier 会存储在当前 LayoutNode 上,等待 measure 的开始参与其中。下面重点分析 MeasurePolicy 了解三步走如何实现。

3. MeasurePolicy - 测量策略

fun interface MeasurePolicy {

    fun MeasureScope.measure(
        measurables: List<Meas