
一、概念
1.1 三阶段


Compose 通过三个阶段把数据转化为UI:组合(显示什么,生成UI树,这也是相对于View多出来的一个阶段)、布局(在哪里,有多大)、绘制(如何渲染绘制)。会根据发生了什么变化只运行必要阶段(跳过某阶段优化性能),通过“失效跟踪”来实现,如当状态改变时框架只会将受影响的 LayoutNode 标记为脏,颜色变化只会标记绘制阶段为脏,文本变化会同时标记布局和绘制阶段为脏。
| 组合阶段 Compisition | 可组合函数负责描述UI,LayoutNode负责干活(测量布局绘制)。由状态变化触发界面的自动刷新,但会智能跳过重组。界面渲染时会使用多叉树结构,将可组合函数转化为一个个布局节点( LayoutNode )构建出一颗具有层次结构的节点树,这棵树被称为 Composition。 |
| 布局阶段 Layout | 节点的约束或内容发生变化时触发,测量并摆放,约束向下传播和尺寸向上报告。父节点会测量自己的子节点,然后在一个二维空间里进行摆放。通过从上往下遍历测量(如果存在子节点则测量子节点,测量完子节点后决定自身的尺寸)、从下往上摆放(根据子节点的尺寸摆放子节点)来决定该节点的宽高和坐标。 |
| 绘制阶段 Drawing | 当视觉属性发生变化时触发。基于 Layout 阶段拿到的尺寸和位置信息,绘制在屏上。(通过 Android Cavas API ,底层调用 skia 实现) |
1.2 LayoutNode & ModifierNode
Compose有两个节点系统,协同工作但服务于不同目的。将它们分开使得 Compose 能够跳过某个阶段优化性能(如框架可以更新与绘制相关的修饰符节点而无需重新生成LayoutNode)。
| LayoutNode | 代表了UI的层次结构。每个可组合函数都会转为节点,最终形成一棵树,负责测量布局绘制。AndroidComposeView(ComposeView的内部核心类)会将LayoutNode树转换为绘制指令。底层通过ViewRootImpl(Android原生渲染核心)将绘制指令提交到SurfaceFlinger(安卓图形服务),最终渲染到屏幕。 |
| ModifierNode | 代表了附加在 LayoutNode 上的行为,每个修饰符都会转为节点,最终形成一个链表,负责处理行为(触摸事件、绘制操作、布局约束)。 |
二、组合阶段 Compisition
每个可组合函数都会映射成UI树的 Layout Node 。可组合函数里还可以包含逻辑和控制流(if else, when...),在不同的状态下产生不同的UI树。

2.1.1 Modifier 链式调用
使用 Modifier 可以更改外观,当链式调用 Modifier 的时候,先调用的会包裹后调用的,最里层是 Layout Node。详见

三、布局阶段 Layout
| Measure Children 测量子节点 | 遍历子节点,测量它们的尺寸。 |
| Decide own size 确定(计算)自身尺寸 | 根据收集的子节点尺寸,决定当前节点自己的尺寸。 |
| Place children 摆放(定位)子节点 | 将子节点摆放到相对位置。 |

从上往下测量,从下往上摆放:
- 系统要求根节点 Row 测量自身。
- 根节点 Row 要求第一个子元素 Image 测量自身。
- 由于 Image 是叶子节点(没有子节点)能确定自身的尺寸和摆放并上报。
- 根节点 Row 要求第二个子元素 Column 测量自身。由于 Column 是分支节点(有子节点)需要先测量所有子元素来确定自身。
- 父容器 Column 要求第一个子元素 Text 测量自身。
- 由于 Text 是叶子节点能确定自身的尺寸和摆放并上报。
- 父容器 Column 要求第二个子元素 Text 测量自身。
- 由于 Text 是叶子节点能确定自身的尺寸和摆放并上报。
- 父容器 Column 所有子元素都测量摆放完毕,可以确定自身的尺寸和摆放并上报。
- 根节点 Row 所有子元素都测量摆放完毕,可以确定自身的尺寸和摆放。
四、ModifierNode

Modifier 在组合阶段也会成为 Node 存储在节点树上,Modifier 的调用链生成一条单向继承的子节点树,而被修饰的 LayoutNode 会成为这条树枝的叶子节点。
如图 Image 最终成为 clip→size 的子节点,挂在节点树上的 ModifierNode 可以参与到深度遍历的绘制流程中,在 Image 之前对 Constraints 做出调整,完成对末端 Image 的装饰。
组合阶段, Modifier#then() 创建 Element 加入 ModifierChain 中。Element 是无状态的,重组中会重新生成,Element 会在组合中创建有状态的 ModifierNode。ModifierNode 有状态,重组中仅当状态发生变化时被更新,否则不会重新生成。ModifierNode 是 Compose 1.5 引入的新优化,目的就是通过存储 Modifier 状态参与比较,提升重组性能。
ModifierNode 按照参与的阶段不同,分为 LayoutModifierNode 和 DrawModifierNode。
4.1 LayoutModifierNode
Modifier.layout() 也能自定义布局,与 Layout() 方式唯一的区别是接受单个 measurable 参数而不是 List,因为 ModifierNode 是单向链表,所以只会有一个后续子节点。如果把 Layout() 的 measure 看做是自定义 ViewGroup 需要针对多个子 View 布局,那么 Modifier.layout() 的 measure 更像是自定义 View,只对自身负责。
| fun Modifier.layout( measure: MeasureScope.(Measurable, Constraints) -> MeasureResult ) |
4.1.1 基本使用
Box(
Modifier.layout { measurable, constraints ->
//垂直方向增加边距
val placeable = measurable.measure(constraints)
val verticalPadding = 50
layout(width = placeable.width, height = placeable.height + verticalPadding * 2) {
placeable.placeRelative(0, verticalPadding)
}
}
)
4.1.2 封装使用
fun Modifier.XXX(): Modifier = then(
layout { measurable, constraints ->
//测量自身并返回结果
//通过contraints可以拿到自身宽或高可以设置的最大和最小值
val placeable = measurable.measure(constraints)
//获取测量后的控件宽高
val measuredWidth = placeable.width
val measuredHeight = placeable.height
//计算实际宽高
val needWidth = ...
val needHeight = ...
//指定控件宽高
layout(needWidth, needHeight) {
//指定摆放的左上角偏移(在xy上移动的距离)
placeable.placeRelative(0, 0)
}
}
)
4.2 DrawModifierNode
五、绘制阶段 Drawing
同样地,UI树会自顶向下地遍历,每个节点依次在屏幕上绘制自身。首先Row会绘制它自己的内容如背景。然后 Image 绘制自身,再之后到分支节点Column,Column的第一个Text,Column的第二个Text。

本文详细解释了Compose框架的三个关键阶段:组合(将数据转化为UI树),布局(测量和摆放节点),以及绘制(节点在屏幕上的渲染)。通过例子说明了Modifier的链式调用、Contraints的约束传递,以及UI树的自顶向下遍历过程。
4073

被折叠的 条评论
为什么被折叠?



