Compose 基础 - 数据转UI的三阶段(组合、布局、绘制)

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

一、概念

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

详见:Compose 基础 - 组合阶段

每个可组合函数都会映射成UI树的 Layout Node 。可组合函数里还可以包含逻辑和控制流(if else, when...),在不同的状态下产生不同的UI树。

2.1.1 Modifier 链式调用

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

三、布局阶段 Layout

详见:Compose 自定义 - 布局阶段 Layout

Measure Children

测量子节点 

遍历子节点,测量它们的尺寸。

Decide own size

确定(计算)自身尺寸

根据收集的子节点尺寸,决定当前节点自己的尺寸。

Place children

摆放(定位)子节点

将子节点摆放到相对位置。

从上往下测量,从下往上摆放: 

  1. 系统要求根节点 Row 测量自身。
  2. 根节点 Row 要求第一个子元素 Image 测量自身。
  3. 由于 Image 是叶子节点(没有子节点)能确定自身的尺寸和摆放并上报。
  4. 根节点 Row 要求第二个子元素 Column 测量自身。由于 Column 是分支节点(有子节点)需要先测量所有子元素来确定自身。
  5. 父容器 Column 要求第一个子元素 Text 测量自身。
  6. 由于 Text 是叶子节点能确定自身的尺寸和摆放并上报。
  7. 父容器 Column 要求第二个子元素 Text 测量自身。
  8. 由于 Text 是叶子节点能确定自身的尺寸和摆放并上报。
  9. 父容器 Column 所有子元素都测量摆放完毕,可以确定自身的尺寸和摆放并上报。
  10. 根节点 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

详见:Compose 自定义 - 绘制阶段 Draw

同样地,UI树会自顶向下地遍历,每个节点依次在屏幕上绘制自身。首先Row会绘制它自己的内容如背景。然后 Image 绘制自身,再之后到分支节点Column,Column的第一个Text,Column的第二个Text。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值