当前位置:   article > 正文

Compose知识分享

Compose知识分享

前言

“Jetpack Compose 是一个适用于 Android 的新式声明性界面工具包。Compose 提供声明性 API,让您可在不以命令方式改变前端视图的情况下呈现应用界面,从而使编写和维护应用界面变得更加容易。” 

以上是Compose官网中对于Compose这套全新的Android UI库的介绍,Compose的到来为Android引入了声明式、响应式的全新开发方式,直接以函数调用的形式使用组件,取代了原先冗长的Activity定义以及.xml文件编写;这一点也是当前TDF团队Kuikly框架同样所具备的能力,给予声明式、响应式的特性加速用户开发,并且Kuikly直接复用了Android的view体系,与Compose以全新的Composable以及Compose树而言,一定程度上可以加速新用户对于底层的理解。​​​​​​​

Compose执行生命周期

我们继续Compose,Compose在声明式函数的基础上,引入了重组的能力,结合后续的度量策略,Compose UI在发生变化时,会被系统的快照系统SnapManager监测到,并使用协程管道的方式获取信息,然后执行重组动作,重组动作会对状态变更的UI元素进行重新渲染,而不修改状态未改变的其余UI元素。

Compose程序的执行之后,一直到页面渲染出结果以及后续的持续UI变更,共可以分为4个阶段,分别是“组合 --> 布局  --> 渲染 <--> 重组”;

组合阶段

“在组合阶段,Compose 运行时会执行可组合函数,并 输出一个表示界面的树结构。这个界面树由 包含下一阶段所需的所有信息的布局节点”(官方文档)

因此我们不难理解,Compose仍然是采用了树型结构作为UI加载、布局、渲染的底层结构,但是有所变更的是如今的Compose树的节点类型为ComposeUiNode,在组件树真正开始被载入时会变成LayoutNode类型的节点。LayoutNode是ComposeUiNode的子类,其继承了ComposeUiNode中所有组件设置的属性。对应的Row、Image、Column以及Text组件所形成的LayoutNode树显示如下:

因此我们可以明确了第一点,即组合阶段就是产生所有组件的LayoutNode树,为后续布局阶段提供遍历的内容。

结合源码我们梳理了组合阶段的执行流程,显示如下:

相关源码展示如下,setContent中内部的函数调用在此不做展示;可以结合流程图去检索源码即可了解其中的调用链。

  1. private object TutorialSnippet1 {
  2.     class MainActivity : AppCompatActivity() {
  3.         override fun onCreate(savedInstanceState: Bundle?) {
  4.             super.onCreate(savedInstanceState)
  5.             setContent {
  6.                 Text("Hello world!")
  7.             }
  8.         }
  9.     }
  10. }
  11. public fun ComponentActivity.setContent(
  12.     parent: CompositionContext? = null,
  13.     content: @Composable () -> Unit
  14. ) {
  15.     val existingComposeView = window.decorView
  16.         .findViewById<ViewGroup>(android.R.id.content)
  17.         .getChildAt(0) as? ComposeView
  18.     if (existingComposeView != null) with(existingComposeView) {
  19.         setParentCompositionContext(parent)
  20.         setContent(content)
  21.     } else ComposeView(this).apply {
  22.         // Set content and parent **before** setContentView
  23.         // to have ComposeView create the composition on attach
  24.         setParentCompositionContext(parent)
  25.         setContent(content)
  26.         // Set the view tree owners before setting the content view so that the inflation process
  27.         // and attach listeners will see them already present
  28.         setOwners()
  29.         setContentView(this, DefaultActivityContentLayoutParams)
  30.     }
  31. }

布局阶段

在布局阶段,Compose 会使用在组合阶段生成的界面树 作为输入,计算每个布局节点在界面树中的测量和放置位置,此过程中将遍历每个LayoutNode节点执行子节点测量、自我测量以及放置子节点三个过程。

以上图的LayoutNode节点树为例,此过程可描述为:

1、Row 测量其子项 Image 和 Column。

2、系统测量了 Image。它没有任何子元素,因此它决定自己的 尺寸,并将尺寸报告给 Row。

3、接下来测量 Column。它会测量自己的子项(两个 Text 可组合项)。

4、系统将测量第一个 Text。由于没有任何子元素,因此它决定 自己的尺寸,并将其尺寸报告给 Column。系统测量第二个 Text。由于没有任何子元素,因此它决定 并将其报告给 Column。

5、Column 使用子测量值来确定自己的尺寸。它使用 最大子元素宽度及其子元素高度之和。

6、Column 会相对于自身放置其子项,并将其置于子项下方 相互垂直

7、Row 使用子测量值来确定自己的尺寸。它使用 子元素的最大高度及其子元素宽度之和。然后 子项。

采用深度遍历的方式执行LayoutNode树的遍历,基于constrains约束,树上每个组件所在的位置在遍历完成后都将计算完成,即完成布局阶段,等待后续的渲染阶段。

而布局阶段中,Modifier类已经开始在遍历的时候就被加载,并在宽高计算时被认为是待度量的因素。具体各组件的度量策略我附上一张表格。

!!! 目前核心关心的是Modifier属性是怎么被加载的?

① 组成Modifier链

  1. Surface(
  2.         modifier = Modifier
  3.             .size(200.dp) // 设置组件大小
  4.             .background(Color.LightGray) // 设置背景颜色
  5.             .offset(x = 10.dp, y = 20.dp) // 设置偏移量
  6.     ) {
  7.         Text(
  8.             text = "Hello, Jetpack Compose!",
  9.             fontSize = 20.sp,
  10.             color = Color.Black,
  11.             modifier = Modifier.padding(8.dp) // 内部文本的额外内边距
  12.         )
  13.     }

以此代码为例,Surface组件使用Modifier伴生对象基于链式调用的方式声明了多个属性,也正如此Modifier是Compose中修饰 UI 组件的关键数据结构。

  1. interface Modifier {
  2.     fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
  3.     fun <R> foldOut(initial: R, operation: (Element, R) -> R): R
  4.     fun any(predicate: (Element) -> Boolean): Boolean
  5.     fun all(predicate: (Element) -> Boolean): Boolean
  6.     infix fun then(other: Modifier): Modifier = ...
  7.     interface Element : Modifier {
  8.         ...
  9.     }
  10.     companion object : Modifier {
  11.         ...
  12.     }
  13. }

Modifier 接口有三个直接实现类或接口:伴生对象 Modifier、内部子接口Modifier.Element、CombinedModifier

  1. 伴生对象 Modifier:最常用的 Modifier即在代码中使用 Modifier.xxx()
  2. 内部子接口 Modifier.Element:当我们使用Modifier.xxx()时,其内部实际会创建一个Modifier 实例。如使用 Modifier.size(100.dp) 时,内部会创建一个 SizeModifier 实例;

其背后的继承关系是

而类似LayoutModifier的中间类显示如下,不同的中间Modifier.Element子类将使得Modifier链建立时使用CombinedModifier来链接;

② Modifier链的构建过程

  1. then()

size属性定义时建立的SizeModifier实例被当作参数传入 then() 方法中。而这个 then() 方法就是 Modifier 间相互连接的关键方法。

  1. Modifier.size(100.dp)
  2. fun Modifier.size(size: Dp) = this.then( // 关键方法
  3.     SizeModifier(
  4.         ...
  5.     )
  6. )
  7. // 返回待连接的Modifier
  8. companion object : Modifier {
  9.     ...
  10.     override infix fun then(other: Modifier): Modifier = other
  11. }
  1. 继续链式调用后续属性,BackGround, padding

  1. Modifier
  2.     .size(100.dp)
  3.     .background(Color.Red)
  4. fun Modifier.background(
  5.     color: Color,
  6.     shape: Shape = RectangleShape
  7. ) = this.then( // 当前 this 指向 SizeModifier 实例
  8.     Background(
  9.         ...
  10.     )
  11. )
  1. 而此时Modifier链也将引入新的类型CombinedModifier来链接所对应的中间Modifier.Element子类不一致时的Modifier

  1. interface Modifier {
  2.     infix fun then(other: Modifier): Modifier =
  3.     if (other === Modifier) this else CombinedModifier(this, other)
  4. }
  5. class CombinedModifier(
  6.     private val outer: Modifier,
  7.     private val inner: Modifier
  8. ) : Modifier

形成以下的样式

所有属性全部上链会形成

至此Modifier链已经构建完成,而在LayoutNode遍历时会基于nodes属性访问管理Modifier链的Nodechain

internal val nodes = NodeChain(this)

而辅助遍历时的数据结构是

  1. internal val innerCoordinator: NodeCoordinator
  2. get() = nodes.innerCoordinator
  3. internal val layoutDelegate = LayoutNodeLayoutDelegate(this)   
  4. internal val outerCoordinator: NodeCoordinator
  5. get() = nodes.outerCoordinator

innerCoordinator是指最内层的协调器,直接与LayoutNode关联,负责处理Modifier链的内部操作,包括处理内部Modifier、管理LayoutNode子LayoutNode,协助他们的度量工作以及传递事件;

outerCoordinator是指最外层的协调器,负责处理Modifier链的外部操作,包括处理外部Modifier,协调Modifier的调用顺序以及管理LayoutNode父节点;

其中内部Modifier和外部Modifier的分辨规则是:

  1. 内部 Modifier 是指那些主要影响组件的布局和测量的修饰符。这些修饰符通常在LayoutNode的内部进行操作,改变组件的尺寸、位置和布局行为。( padding, size, fillMaxWidth / fillMaxHeight, wrapContentSize, aspectRatio, weight)
  2. 外部 Modifier 是指那些主要影响组件的绘制和偏移的修饰符。这些修饰符通常在LayoutNode的外部进行操作,改变组件的外观、透明度和位置偏移等。( background, border, offset, alpha, clip, shadow);

而其中访问Modifier链的过程将基于foldin与foldout方法取到Modifier上所有的设置信息,从而将外部 or 内部 属性的定义值反馈到当前LayoutNode的width, height上,并最后在父节点的constrains限制下输出最大的width和height;

  1. fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
  2. // foldIn(): 正向遍历 Modifier 链,SizeModifier-> Background -> PaddingModifier
  3. fun <R> foldOut(initial: R, operation: (Element, R) -> R): R
  4. // foldOut(): 反向遍历 Modifier 链, PaddingModifier -> Background ->SizeModifier

绘制阶段

绘制代码期间的状态读取会影响绘制阶段。常见示例包括 Canvas()Modifier.drawBehind Modifier.drawWithContent

Modifier知识补充

Compose UI组件 Modifier子类 类图

Compose 常用UI组件 LayoutNode、Modifier、MeasurePolicy汇总

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/1019268
推荐阅读
相关标签
  

闽ICP备14008679号