Android Jetpack Compose介绍

打印 上一主题 下一主题

主题 1781|帖子 1781|积分 5343

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Android Jetpack Compose

Android Jetpack Compose 是 Google 推出的现代 UI 工具包,用于以声明式的方式构建 Android 应用的 UI。它摒弃了传统的 XML 结构方式,完全基于 Kotlin 编写,提供了更简洁、更强大的 UI 开发体验。以下是 Compose 的使用方式、原理和焦点概念的详细剖析。
1. Compose 的焦点概念

1.1 声明式 UI



  • Compose 采用声明式编程范式,开发者只需描述 UI 应该是什么样子,而不需要关心其具体实现。
  • 与传统的命令式 UI(如 XML + View 体系)不同,Compose 的 UI 是动态的,可以根据状态自动更新。
1.2 Composable 函数



  • Compose 的 UI 由 @Composable 函数界说。这些函数是纯函数,接收输入参数并返回 UI 组件。
  • 例如:
    1. @Composable
    2. fun Greeting(name: String) {
    3.     Text(text = "Hello, $name!")
    4. }
    复制代码
1.3 状态管理



  • Compose 使用 State 来管理 UI 的状态。当状态发生变革时,Compose 会自动重新绘制相干的 UI。
  • 例如:
    1. @Composable
    2. fun Counter() {
    3.     val count = remember { mutableStateOf(0) }
    4.     Button(onClick = { count.value++ }) {
    5.         Text("Clicked ${count.value} times")
    6.     }
    7. }
    复制代码
1.4 重组(Recomposition)



  • 当状态发生变革时,Compose 会触发重组(Recomposition),重新调用相干的 @Composable 函数来更新 UI。
  • Compose 会自动优化重组过程,只更新需要变革的部门。
2. Compose 的根本使用

2.1 设置项目

在 build.gradle 中添加 Compose 的依赖:
  1. dependencies {
  2.     implementation "androidx.compose.ui:ui:1.3.3"
  3.     implementation "androidx.compose.material:material:1.3.3"
  4.     implementation "androidx.compose.runtime:runtime:1.3.3"
  5.     implementation "androidx.activity:activity-compose:1.6.1"
  6. }
复制代码
2.2 创建 Composable 函数

界说一个简朴的 UI 组件:
  1. @Composable
  2. fun Greeting(name: String) {
  3.     Text(text = "Hello, $name!")
  4. }
复制代码
2.3 在 Activity 中使用 Compose

在 Activity 中设置 Compose 的内容:
  1. class MainActivity : ComponentActivity() {
  2.     override fun onCreate(savedInstanceState: Bundle?) {
  3.         super.onCreate(savedInstanceState)
  4.         setContent {
  5.             Greeting(name = "Compose")
  6.         }
  7.     }
  8. }
复制代码
3. Compose 的高级特性

3.1 结构

Compose 提供了多种结构组件,如 Column、Row、Box 等:
  1. @Composable
  2. fun ProfileCard() {
  3.     Column {
  4.         Text("John Doe")
  5.         Text("Software Engineer")
  6.     }
  7. }
复制代码
3.2 主题

可以通过 MaterialTheme 界说应用的主题:
  1. @Composable
  2. fun App() {
  3.     MaterialTheme {
  4.         Greeting(name = "Compose")
  5.     }
  6. }
复制代码
3.3 动画

Compose 提供了强大的动画支持:
  1. @Composable
  2. fun AnimatedButton() {
  3.     val enabled = remember { mutableStateOf(true) }
  4.     Button(onClick = { enabled.value = !enabled.value }) {
  5.         Text(if (enabled.value) "Enabled" else "Disabled")
  6.     }
  7. }
复制代码
3.4 状态提拔

将状态提拔到父组件中,以实现更机动的状态管理:
  1. @Composable
  2. fun Parent() {
  3.     val count = remember { mutableStateOf(0) }
  4.     Counter(count = count.value, onIncrement = { count.value++ })
  5. }
  6. @Composable
  7. fun Counter(count: Int, onIncrement: () -> Unit) {
  8.     Button(onClick = onIncrement) {
  9.         Text("Clicked $count times")
  10.     }
  11. }
复制代码
4. Compose 的工作原理

4.1 Compose 的架构



  • Compose 基于 Kotlin 的编译器插件,将 @Composable 函数转换为高效的 UI 渲染代码。
  • Compose 使用 Slot TableGap Buffer 技能来管理 UI 的状态和更新。
4.2 重组机制



  • 当状态发生变革时,Compose 会标志受影响的 @Composable 函数,并重新调用它们。
  • Compose 会通过比力前后两次调用的参数,决定是否需要重组。
4.3 状态管理



  • Compose 使用 remember 和 mutableStateOf 来保存和更新状态。
  • 状态的变革会触发重组,从而更新 UI。
4.4 结构和绘制



  • Compose 的结构体系基于 ConstraintLayoutMeasure 机制,支持机动的 UI 结构。
  • 绘制过程使用 GPU 加速,性能高效。

前置学习

Kotlin remember

remember用于在组件的重新组合(Recomposition)过程中保留状态或计算结果,避免不必要的重复计算或初始化。
remember 的作用

remember 的作用是缓存一个值,并在组件的多次重新组合中保持该值稳定,除非它的依赖项发生了变革。它通常用于管理组件的内部状态或缓存昂贵的计算结果。
remember 的语法

remember 的常见用法有两种:

  • 不带依赖项的 remember
    1. val value = remember { initialValue }
    复制代码

    • 这里的 initialValue 是一个 lambda 表达式,它会在第一次组合时执行,并将结果缓存。在后续的重新组合中,remember 会直接返回缓存的值,而不会重新执行 lambda。

  • 带依赖项的 remember
    1. val value = remember(key1, key2, ...) { initialValue }
    复制代码

    • 这里的 key1、key2 等是依赖项。当这些依赖项发生变革时,remember 会重新执行 lambda 并更新缓存的值;否则,直接返回缓存的值。

remember 的示例


  • 缓存状态
    1. @Composable
    2. fun Counter() {
    3.     val count = remember { mutableStateOf(0) } // 缓存一个状态
    4.     Button(onClick = { count.value++ }) {
    5.         Text("Clicked ${count.value} times")
    6.     }
    7. }
    复制代码

    • 这里使用 remember 缓存了一个 MutableState,确保在重新组合时不会重新初始化 count。

  • 缓存计算结果
    1. @Composable
    2. fun ExpensiveCalculation(input: Int) {
    3.     val result = remember(input) { // 缓存计算结果,依赖 input
    4.         performExpensiveCalculation(input)
    5.     }
    6.     Text("Result: $result")
    7. }
    复制代码

    • 这里使用 remember 缓存了一个昂贵的计算结果,只有当 input 发生变革时才会重新计算。

  • 缓存对象
    1. @Composable
    2. fun MyComponent() {
    3.     val myObject = remember { MyObject() } // 缓存一个对象
    4.     Text("Object: $myObject")
    5. }
    复制代码

    • 这里使用 remember 缓存了一个对象,避免在每次重新组合时重新创建。

remember 的注意事项


  • remember 只能在 @Composable 函数中使用,由于它依赖于 Compose 的重组机制。
  • remember 缓存的值只在当前组件的生命周期内有用。假如组件被移除或重修,缓存的值会丢失。
  • remember 不能用于跨组件的状态共享,假如需要跨组件共享状态,应该使用 rememberSaveable 或 ViewModel。
remember 与 rememberSaveable

rememberSaveable 是 remember 的增强版,它可以在配置更改(如屏幕旋转)或进程重修时保存状态。例如:
  1. @Composable
  2. fun MyComponent() {
  3.     val state = rememberSaveable { mutableStateOf(0) }
  4.     Button(onClick = { state.value++ }) {
  5.         Text("Clicked ${state.value} times")
  6.     }
  7. }
复制代码
Compose添加动画

1. 状态驱动的动画

Compose 的动画是基于状态驱动的。通过监听状态的变革,Compose 会自动在状态之间进行平滑的过渡。
示例:简朴的淡入淡出动画

  1. @Composable
  2. fun FadeInOutAnimation() {
  3.     var visible by remember { mutableStateOf(true) } // 状态控制显示/隐藏
  4.     val alpha by animateFloatAsState(
  5.         targetValue = if (visible) 1f else 0f, // 目标透明度
  6.         animationSpec = tween(durationMillis = 1000) // 动画配置
  7.     )
  8.     Column {
  9.         Button(onClick = { visible = !visible }) {
  10.             Text("Toggle Visibility")
  11.         }
  12.         Box(
  13.             modifier = Modifier
  14.                 .fillMaxWidth()
  15.                 .height(100.dp)
  16.                 .background(Color.Blue)
  17.                 .alpha(alpha) // 应用透明动画
  18.         )
  19.     }
  20. }
复制代码


  • animateFloatAsState:用于在状态变革时平滑过渡一个 Float 值。
  • tween:界说动画的持续时间和缓动效果。
2. 过渡动画

Transition 用于在多个状态之间进行复杂的动画过渡。
示例:切换大小和颜色的动画

  1. @Composable
  2. fun TransitionAnimation() {
  3.     var toggled by remember { mutableStateOf(false) }
  4.     val transition = updateTransition(targetState = toggled, label = "Toggle Transition")
  5.     val size by transition.animateDp(
  6.         transitionSpec = { tween(durationMillis = 500) },
  7.         label = "Size Animation",
  8.         targetValueByState = { isToggled -> if (isToggled) 200.dp else 100.dp }
  9.     )
  10.     val color by transition.animateColor(
  11.         transitionSpec = { tween(durationMillis = 500) },
  12.         label = "Color Animation",
  13.         targetValueByState = { isToggled -> if (isToggled) Color.Red else Color.Blue }
  14.     )
  15.     Column {
  16.         Button(onClick = { toggled = !toggled }) {
  17.             Text("Toggle Animation")
  18.         }
  19.         Box(
  20.             modifier = Modifier
  21.                 .size(size)
  22.                 .background(color)
  23.         )
  24.     }
  25. }
复制代码


  • updateTransition:创建一个过渡动画对象。
  • animateDp 和 animateColor:分别在状态之间过渡 Dp 和 Color 值。
3. 无限循环动画

使用 InfiniteTransition 可以创建无限循环的动画。
示例:无限旋转的圆圈

  1. @Composable
  2. fun InfiniteRotationAnimation() {
  3.     val infiniteTransition = rememberInfiniteTransition()
  4.     val rotation by infiniteTransition.animateFloat(
  5.         initialValue = 0f,
  6.         targetValue = 360f,
  7.         animationSpec = infiniteRepeatable(
  8.             animation = tween(durationMillis = 1000, easing = LinearEasing),
  9.             repeatMode = RepeatMode.Restart
  10.         )
  11.     )
  12.     Box(
  13.         modifier = Modifier
  14.             .fillMaxSize()
  15.             .wrapContentSize(Alignment.Center)
  16.     ) {
  17.         Box(
  18.             modifier = Modifier
  19.                 .size(100.dp)
  20.                 .background(Color.Blue)
  21.                 .graphicsLayer {
  22.                     rotationZ = rotation // 应用旋转动画
  23.                 }
  24.         )
  25.     }
  26. }
复制代码


  • infiniteRepeatable:界说一个无限循环的动画。
4. 手势驱动的动画

Compose 支持将动画与手势联合,实现更自然的交互效果。
示例:拖动动画

  1. @Composable
  2. fun DraggableBox() {
  3.     var offsetX by remember { mutableStateOf(0f) }
  4.     var offsetY by remember { mutableStateOf(0f) }
  5.     Box(
  6.         modifier = Modifier
  7.             .fillMaxSize()
  8.             .pointerInput(Unit) {
  9.                 detectDragGestures { change, dragAmount ->
  10.                     offsetX += dragAmount.x
  11.                     offsetY += dragAmount.y
  12.                 }
  13.             }
  14.     ) {
  15.         Box(
  16.             modifier = Modifier
  17.                 .offset { IntOffset(offsetX.toInt(), offsetY.toInt()) }
  18.                 .size(100.dp)
  19.                 .background(Color.Red)
  20.         )
  21.     }
  22. }
复制代码


  • pointerInput 和 detectDragGestures:用于监听拖动事件。
  • offset:根据拖动间隔更新组件的位置。
5. 自界说动画

假如需要更复杂的动画,可以使用 Animatable 或 AnimationState 进行自界说控制。
示例:使用 Animatable 实现弹簧动画

  1. @Composable
  2. fun SpringAnimation() {
  3.     val animatable = remember { Animatable(0f, Dp.VectorConverter) }
  4.     LaunchedEffect(Unit) {
  5.         animatable.animateTo(
  6.             targetValue = 200.dp.value,
  7.             animationSpec = spring(
  8.                 dampingRatio = Spring.DampingRatioLowBouncy,
  9.                 stiffness = Spring.StiffnessLow
  10.             )
  11.         )
  12.     }
  13.     Box(
  14.         modifier = Modifier
  15.             .offset { IntOffset(0, animatable.value.toInt()) }
  16.             .size(100.dp)
  17.             .background(Color.Green)
  18.     )
  19. }
复制代码


  • Animatable:用于创建自界说动画。
  • spring:界说弹簧动画的特性。
6. 动画的可见性

Compose 提供了 AnimatedVisibility,可以方便地实现组件的体现和隐藏动画。
示例:滑动体现/隐藏动画

  1. @Composable
  2. fun AnimatedVisibilityExample() {
  3.     var visible by remember { mutableStateOf(true) }
  4.     Column {
  5.         Button(onClick = { visible = !visible }) {
  6.             Text("Toggle Visibility")
  7.         }
  8.         AnimatedVisibility(
  9.             visible = visible,
  10.             enter = slideInVertically() + fadeIn(),
  11.             exit = slideOutVertically() + fadeOut()
  12.         ) {
  13.             Box(
  14.                 modifier = Modifier
  15.                     .fillMaxWidth()
  16.                     .height(100.dp)
  17.                     .background(Color.Blue)
  18.             )
  19.         }
  20.     }
  21. }
复制代码


  • AnimatedVisibility:根据状态控制组件的体现和隐藏。
  • slideInVertically 和 fadeIn:界说进入动画。
Slot Table

Slot Table 是 Jetpack Compose 中用于管理组件状态和结构的关键机制。它是 Compose 运行时(Compose Runtime)的焦点部门,负责存储和更新组件的状态、结构信息以及其他元数据。明白 Slot Table 的工作原理有助于更好地把握 Compose 的内部机制。
1. Slot Table 的作用

Slot Table 是 Compose 用于存储组件树(Composable Tree)的底层数据结构。它的重要功能包罗:


  • 存储组件状态:保存 Composable 函数的状态(如 mutableStateOf)。
  • 管理组件结构:记载组件的条理结构和结构信息。
  • 支持重组(Recomposition):在状态变革时,Compose 通过 Slot Table 高效地更新 UI。
2. Slot Table 的结构

Slot Table 可以明白为一个二维表格,其中包罗以下部门:


  • Slots:表格中的单位格,用于存储数据(如状态、结构信息等)。
  • Groups:表格中的行,表示一个 Composable 函数或组件。每个 Group 包罗多个 Slots。
示例:简朴的 Slot Table

假设有以下 Composable 函数:
  1. @Composable
  2. fun MyComponent() {
  3.     val count = remember { mutableStateOf(0) }
  4.     Text("Count: ${count.value}")
  5. }
复制代码
在 Slot Table 中,它大概被表示为:
GroupSlot 1Slot 2MyComponentcount (state)Text (content)Text“Count: 0”-

  • MyComponent 是一个 Group,它包罗两个 Slots:count(状态)和 Text(子组件)。
  • Text 是另一个 Group,它包罗一个 Slot:"Count: 0"(文本内容)。
3. Slot Table 的工作原理

初始组合(Initial Composition)

当 Composable 函数首次执行时,Compose 会:

  • 创建 Slot Table,并记载组件树的结构和状态。
  • 将状态和结构信息存储在 Slots 中。
重组(Recomposition)

当状态发生变革时,Compose 会:

  • 根据 Slot Table 中的信息,判定哪些组件需要更新。
  • 重新执行受影响的 Composable 函数,并更新 Slot Table 中的 Slots。
  • 仅更新 UI 中发生变革的部门,避免不必要的重新绘制。
示例:重组过程

假设 count 从 0 变为 1,Compose 会:

  • 找到 MyComponent 的 Group,并更新 count 的 Slot。
  • 重新执行 Text Composable,并更新其 Slots。
  • 终极,UI 中的文本从 "Count: 0" 更新为 "Count: 1"。
4. Slot Table 的优势



  • 高效的重组:通过 Slot Table,Compose 可以精确地知道哪些部门需要更新,避免不必要的重新绘制。
  • 状态管理:Slot Table 集中管理全部组件的状态,确保状态的一致性和可猜测性。
  • 结构清晰:Slot Table 以树形结构记载组件条理,便于调试和分析。
5. 与 Slot Table 相干的概念

Positional Memoization

Compose 使用位置记忆(Positional Memoization)来跟踪 Composable 函数的执行。每个 Composable 在 Slot Table 中都有一个固定的位置,Compose 通过位置来识别和更新组件。
Gap Buffer

Slot Table 使用间隙缓冲区(Gap Buffer)来高效地插入和删除数据。这种数据结构使得 Composable 的增删操作更加高效。
State

状态(State)是 Slot Table 中存储的重要数据。Compose 通过 mutableStateOf 等 API 将状态存储在 Slot Table 中,并在状态变革时触发重组。

源码分析

1. Compose 的架构

Compose 的源码分为 编译器插件运行时库 两部门:
1.1 Compose 编译器插件

Compose 编译器插件是 Compose 的焦点,负责将 @Composable 函数转换为高效的 UI 渲染代码。它的重要功能包罗:


  • 代码天生:将 @Composable 函数转换为可执行的 UI 渲染逻辑。
  • 状态管理:自动插入状态管理和重组逻辑。
  • 优化:通过静态分析和优化减少不必要的重组。
1.2 Compose 运行时库

Compose 运行时库提供了 Compose 的焦点功能,包罗:


  • Slot Table:用于存储 UI 组件的状态和条理结构。
  • Recomposition:管理 UI 的重组逻辑。
  • Layout 和 Drawing:负责 UI 的结构和绘制。
2. Compose 的焦点机制

2.1 Slot Table



  • 作用:Slot Table 是 Compose 的焦点数据结构,用于存储 UI 组件的状态和条理结构。
  • 实现:它是一个线性表,存储了全部组件的状态和属性。
  • 优化:通过 Gap Buffer 技能高效管理插入、删除和更新操作。
2.2 Recomposition(重组)



  • 触发条件:当状态(如 State 或 MutableState)发生变革时,Compose 会触发重组。
  • 过程

    • 标志受影响的 @Composable 函数。
    • 重新调用这些函数,天生新的 UI 组件树。
    • 比力新旧组件树,只更新变革的部门。

  • 优化:Compose 通过静态分析和缓存机制减少不必要的重组。
2.3 状态管理



  • remember:用于在重组之间保存状态。
  • mutableStateOf:用于创建可变状态,当状态变革时触发重组。
  • 状态提拔:将状态提拔到父组件中,以实现更机动的状态管理。
3. Compose 的关键源码剖析

3.1 @Composable 函数

@Composable 函数是 Compose 的根本构建块。编译器插件会将 @Composable 函数转换为以下结构:
  1. @Composable
  2. fun Example() {
  3.     // 原始代码
  4.     Text("Hello, Compose!")
  5. }
  6. // 编译后
  7. fun Example(composer: Composer, key: Int) {
  8.     composer.start(key)
  9.     Text(composer, "Hello, Compose!")
  10.     composer.end()
  11. }
复制代码


  • composer:用于管理 UI 组件的状态和条理结构。
  • key:用于标识组件的唯一性。
1. Composer 的作用

Composer 的重要职责包罗:


  • 管理 Composable 函数的执行:通过调用 Composable 函数,天生 UI 组件树。
  • 状态管理:存储和更新 Composable 函数中的状态(如 mutableStateOf)。
  • 支持重组(Recomposition):在状态变革时,重新执行受影响的 Composable 函数,并更新 UI。
  • 协调结构和绘制:将天生的组件树传递给结构和绘制体系。
2. Composer 的焦点方法

Composer 接口界说了一系列方法,用于管理 Composable 函数的执行和状态存储。以下是关键方法及其作用:
start 和 end

  1. fun start(key: Int, group: Int)
  2. fun end()
复制代码


  • start:标志一个 Composable 函数的开始,并分配一个唯一的 key 和 group。
  • end:标志一个 Composable 函数的结束。
  • 这两个方法用于构建组件树的条理结构。
createNode

  1. fun createNode(factory: () -> T)
复制代码


  • 创建一个 UI 节点(如 LayoutNode 或 TextNode),并将其插入组件树。
setValue

  1. fun setValue(value: Any?)
复制代码


  • 将状态值存储到 Slot Table 中。
changed

  1. fun changed(value: Any?): Boolean
复制代码


  • 检查状态值是否发生变革,以决定是否需要触发重组。
remember

  1. fun <T> remember(value: T): T
复制代码


  • 将值存储在 Slot Table 中,并在后续执行中返回相同的值(除非依赖项发生变革)。
updateScope

  1. fun updateScope(scope: () -> Unit)
复制代码


  • 注册一个更新作用域,用于在重组时重新执行 Composable 函数。
3. Composer 的实现

Composer 的焦点实现是 ComposerImpl 类,它负责具体的逻辑。以下是 ComposerImpl 的关键机制:
Slot Table



  • ComposerImpl 使用 Slot Table 来存储 Composable 函数的状态和元数据。
  • Slot Table 是一个二维表格,包罗 Slots 和 Groups,分别用于存储数据和描述组件结构。
Gap Buffer



  • Slot Table 使用 Gap Buffer 来高效地插入和删除数据,支持动态组件树的构建。
Positional Memoization



  • ComposerImpl 使用 位置记忆 来跟踪 Composable 函数的执行。每个 Composable 函数在 Slot Table 中都有一个固定的位置,Composer 通过位置来识别和更新组件。
重组机制



  • 当状态发生变革时,ComposerImpl 会重新执行受影响的 Composable 函数,并更新 Slot Table 和 UI。
4. Composable 函数的执行流程

以下是 Composable 函数在 Composer 中的执行流程:

  • 开始 Composable 函数

    • 调用 start 方法,分配 key 和 group,标志 Composable 函数的开始。

  • 创建 UI 节点

    • 调用 createNode 方法,天生 UI 节点并插入组件树。

  • 存储状态

    • 调用 setValue 方法,将状态值存储到 Slot Table 中。

  • 检查状态变革

    • 调用 changed 方法,判定是否需要触发重组。

  • 结束 Composable 函数

    • 调用 end 方法,标志 Composable 函数的结束。

  • 触发重组

    • 假如状态发生变革,重新执行受影响的 Composable 函数,并更新 UI。

5. 源码分析示例

以下是一个简朴的 Composable 函数及其在 Composer 中的执行过程:
Composable 函数

  1. @Composable
  2. fun MyComponent() {
  3.     val count = remember { mutableStateOf(0) }
  4.     Text("Count: ${count.value}")
  5. }
复制代码
执行流程


  • 调用 start,标志 MyComponent 的开始。
  • 调用 remember,将 count 存储在 Slot Table 中。
  • 调用 start,标志 Text 的开始。
  • 调用 createNode,创建 TextNode 并插入组件树。
  • 调用 end,标志 Text 的结束。
  • 调用 end,标志 MyComponent 的结束。
3.2 Slot Table

Slot Table 是 Compose 的焦点数据结构,源码位于 androidx.compose.runtime 包中。它的重要实现包罗:


  • SlotTable.kt:界说 Slot Table 的结构和操作。
  • GapBuffer.kt:实现 Gap Buffer 技能,用于高效管理插入和删除操作。
SlotTable 是 Jetpack Compose 运行时的焦点数据结构,用于存储 Composable 函数的状态、组件结构和其他元数据。它是 Compose 实现高效重组(Recomposition)和状态管理的关键组件。通太过析 SlotTable 的源码,可以深入明白 Compose 的内部工作机制。
1. SlotTable 的作用

SlotTable 的重要职责包罗:


  • 存储 Composable 函数的状态:如 mutableStateOf 的值。
  • 记载组件树的结构:描述 Composable 函数的条理关系。
  • 支持高效的重组:在状态变革时,快速定位和更新受影响的组件。
  • 管理动态数据:支持插入、删除和更新操作。
2. SlotTable 的结构

SlotTable 是一个二维表格,包罗以下部门:


  • Slots:表格中的单位格,用于存储数据(如状态、结构信息等)。
  • Groups:表格中的行,表示一个 Composable 函数或组件。每个 Group 包罗多个 Slots。
数据结构

SlotTable 的焦点字段包罗:
  1. class SlotTable {
  2.     private val slots: IntArray // 存储 Slot 数据
  3.     private val groups: IntArray // 存储 Group 元数据
  4.     private var groupsSize: Int // Group 的数量
  5.     private var slotsSize: Int // Slot 的数量
  6. }
复制代码


  • Slots:slots 是一个 IntArray,用于存储具体的数据(如状态值、文本内容等)。
  • Groups:groups 是一个 IntArray,用于存储 Group 的元数据(如范例、父 Group、子 Group 等)。
3. Group 的表示

每个 Group 在 groups 数组中占用了多个连续的槽位,用于存储以下信息:


  • Group 范例:表示 Group 的种类(如 Composable 函数、结构节点等)。
  • 父 Group:指向父 Group 的索引。
  • 子 Group:指向子 Group 的索引。
  • 数据范围:指向 slots 数组中与该 Group 相干的数据。
Group 的结构

在 groups 数组中,一个 Group 的结构如下:
索引字段描述0KeyGroup 的唯一标识符1Parent父 Group 的索引2FirstChild第一个子 Group 的索引3NextSibling下一个兄弟 Group 的索引4DataStart数据在 slots 中的起始索引5DataEnd数据在 slots 中的结束索引 4. SlotTable 的焦点操作

SlotTable 提供了一系列方法,用于管理数据和组件结构。
插入 Group

  1. fun insertGroup(key: Int, parent: Int, dataStart: Int, dataEnd: Int): Int
复制代码


  • 插入一个新的 Group,并返回其索引。
  • 更新 groups 和 slots 数组,确保数据的一致性。
删除 Group

  1. fun removeGroup(group: Int)
复制代码


  • 删除指定的 Group。
  • 更新 groups 和 slots 数组,回收资源。
更新 Slot 数据

  1. fun setSlot(index: Int, value: Any?)
复制代码


  • 更新 slots 数组中指定索引的值。
  • 假如值发生变革,标志 Group 为需要重组。
查找 Group

  1. fun findGroup(key: Int, parent: Int): Int
复制代码


  • 根据 key 和 parent 查找 Group。
  • 假如找到,返回 Group 的索引;否则返回 -1。
5. SlotTable 的重组机制

SlotTable 通过以下机制支持高效的重组:


  • 位置记忆(Positional Memoization)

    • 每个 Composable 函数在 Slot Table 中都有一个固定的位置。
    • 在重组时,Compose 根据位置快速定位和更新组件。

  • Gap Buffer

    • SlotTable 使用 Gap Buffer 来高效地插入和删除数据。
    • Gap Buffer 将数组分为两部门,中央留出一个“间隙”,使得插入和删除操作的时间复杂度为 O(1)。

  • 状态变革检测

    • 当状态发生变革时,SlotTable 会标志受影响的 Group 为“脏数据”,并在重组时重新执行相应的 Composable 函数。

6. 源码分析示例

以下是一个简朴的 Composable 函数及其在 SlotTable 中的表示:
Composable 函数

  1. @Composable
  2. fun MyComponent() {
  3.     val count = remember { mutableStateOf(0) }
  4.     Text("Count: ${count.value}")
  5. }
复制代码
SlotTable 表示

GroupSlotsMyComponentcount (state)Text“Count: 0” 在 groups 和 slots 数组中的具体表示:


  • groups 数组

    • MyComponent:[key=1, parent=-1, firstChild=2, nextSibling=-1, dataStart=0, dataEnd=1]
    • Text:[key=2, parent=1, firstChild=-1, nextSibling=-1, dataStart=1, dataEnd=2]

  • slots 数组

    • [count, "Count: 0"]

3.3 Recomposition

Compose 的重组逻辑位于 androidx.compose.runtime 包中,重要实现包罗:


  • Recomposer.kt:管理重组过程,标志和调度受影响的组件。
  • Composer.kt:负责组件的创建、更新和销毁。
1. Recomposer 的作用

Recomposer 的重要职责包罗:


  • 调度 Composable 函数的执行:协调 Composable 函数的初始执行和重组。
  • 管理状态变革:监听状态的变革,并触发受影响的 Composable 函数进行重组。
  • 协调帧更新:确保 UI 更新与 Android 的帧率(如 60Hz)同步。
  • 支持并发重组:在多个线程中高效地执行 Composable 函数。
2. Recomposer 的焦点机制

Recomposer 的实现依赖于以下焦点机制:
状态快照体系(State Snapshot System)



  • Compose 使用状态快照来跟踪状态的变革。
  • 当状态发生变革时,Recomposer 会查找全部依赖该状态的 Composable 函数,并触发它们重组。
Slot Table



  • Recomposer 使用 Slot Table 来存储 Composable 函数的状态和组件结构。
  • Slot Table 是 Compose 实现高效重组的根本。
帧调度



  • Recomposer 与 Android 的 Choreographer 集成,确保 UI 更新与屏幕刷新率同步。
  • 它会在每一帧开始前执行待处置惩罚的重组任务。
并发重组



  • Recomposer 支持在多个线程中执行 Composable 函数,以进步性能。
  • 它使用线程池来管理并发任务。
3. Recomposer 的源码分析

以下是 Recomposer 的焦点源码片段及其功能剖析:
初始化

Recomposer 在初始化时会创建一个线程池,并注册到 Android 的 Choreographer。
  1. class Recomposer {
  2.     private val choreographer = Choreographer.getInstance()
  3.     private val executor = Executors.newSingleThreadExecutor()
  4.     init {
  5.         choreographer.postFrameCallback(frameCallback)
  6.     }
  7. }
复制代码
帧回调

Recomposer 使用 Choreographer.FrameCallback 来协调帧更新。
  1. private val frameCallback = object : Choreographer.FrameCallback {
  2.     override fun doFrame(frameTimeNanos: Long) {
  3.         // 执行待处理的重组任务
  4.         executePendingRecompositions()
  5.         // 注册下一帧的回调
  6.         choreographer.postFrameCallback(this)
  7.     }
  8. }
复制代码
状态监听

Recomposer 监听状态的变革,并标志受影响的 Composable 函数为“脏数据”。
  1. fun onStateChanged(state: State<*>) {
  2.     // 查找依赖该状态的 Composable 函数
  3.     val affected = findAffectedComposables(state)
  4.     // 标记为需要重组
  5.     markDirty(affected)
  6. }
复制代码
重组调度

Recomposer 使用线程池执行重组任务。
  1. private fun executePendingRecompositions() {
  2.     executor.execute {
  3.         // 获取待处理的重组任务
  4.         val tasks = getPendingRecompositionTasks()
  5.         // 执行任务
  6.         tasks.forEach { it.run() }
  7.     }
  8. }
复制代码
并发重组

Recomposer 支持在多个线程中执行 Composable 函数。
  1. fun recompose(composable: @Composable () -> Unit) {
  2.     executor.execute {
  3.         // 在后台线程中执行 Composable 函数
  4.         composable()
  5.     }
  6. }
复制代码
4. Recomposer 的工作流程


  • 初始化

    • 创建线程池,并注册到 Choreographer。

  • 监听状态变革

    • 当状态发生变革时,标志受影响的 Composable 函数为“脏数据”。

  • 调度重组

    • 在下一帧开始前,执行待处置惩罚的重组任务。

  • 执行 Composable 函数

    • 在后台线程中执行 Composable 函数,并更新 Slot Table。

  • 帧更新

    • 将更新后的 UI 提交给渲染体系。

3.4 Layout 和 Drawing

Compose 的结构和绘制逻辑位于 androidx.compose.ui 包中,重要实现包罗:


  • Layout.kt:界说结构组件,如 Column、Row 等。
  • Draw.kt:实现绘制逻辑,支持自界说绘制。
1. Layout 的作用

Layout 的重要功能包罗:


  • 自界说结构:答应开发者完全控制子组件的丈量(Measure)和结构(Placement)过程。
  • 支持复杂的结构逻辑:例如自界说排列、拖拽结构、流式结构等。
  • 与 Compose 结构体系集成:与 MeasurePolicy 和 LayoutModifier 等组件无缝协作。
2. Layout 的根本用法

Layout 的典型用法如下:
  1. @Composable
  2. fun MyCustomLayout(
  3.     modifier: Modifier = Modifier,
  4.     content: @Composable () -> Unit
  5. ) {
  6.     Layout(
  7.         modifier = modifier,
  8.         content = content
  9.     ) { measurables, constraints ->
  10.         // 测量子组件
  11.         val placeables = measurables.map { measurable ->
  12.             measurable.measure(constraints)
  13.         }
  14.         // 计算布局尺寸
  15.         val layoutWidth = placeables.maxOf { it.width }
  16.         val layoutHeight = placeables.maxOf { it.height }
  17.         // 布局子组件
  18.         layout(layoutWidth, layoutHeight) {
  19.             placeables.forEach { placeable ->
  20.                 placeable.placeRelative(x = 0, y = 0) // 自定义位置
  21.             }
  22.         }
  23.     }
  24. }
复制代码
3. Layout 的源码分析

Layout 是 Compose 中的一个 Composable 函数,其源码位于 androidx.compose.ui.Layout.kt 中。以下是其焦点实现:
Layout 函数签名

  1. @Composable
  2. fun Layout(
  3.     modifier: Modifier = Modifier,
  4.     content: @Composable () -> Unit,
  5.     measurePolicy: MeasurePolicy
  6. )
复制代码


  • modifier:用于调整结构的行为和外貌。
  • content:子组件的 Composable 内容。
  • measurePolicy:界说丈量和结构的逻辑。
MeasurePolicy

MeasurePolicy 是一个接口,界说了如何丈量和结构子组件。它的焦点方法包罗:
  1. interface MeasurePolicy {
  2.     fun MeasureScope.measure(
  3.         measurables: List<Measurable>,
  4.         constraints: Constraints
  5.     ): MeasureResult
  6.     fun IntrinsicMeasureScope.minIntrinsicWidth(
  7.         measurables: List<IntrinsicMeasurable>,
  8.         height: Int
  9.     ): Int
  10.     fun IntrinsicMeasureScope.minIntrinsicHeight(
  11.         measurables: List<IntrinsicMeasurable>,
  12.         width: Int
  13.     ): Int
  14.     fun IntrinsicMeasureScope.maxIntrinsicWidth(
  15.         measurables: List<IntrinsicMeasurable>,
  16.         height: Int
  17.     ): Int
  18.     fun IntrinsicMeasureScope.maxIntrinsicHeight(
  19.         measurables: List<IntrinsicMeasurable>,
  20.         width: Int
  21.     ): Int
  22. }
复制代码
最重要的方法是 measure,它负责丈量子组件并返回结构结果。
Layout 的实现

Layout 的实现如下:
  1. @Composable
  2. fun Layout(
  3.     modifier: Modifier = Modifier,
  4.     content: @Composable () -> Unit,
  5.     measurePolicy: MeasurePolicy
  6. )
  7. {    val density = LocalDensity.current    val layoutDirection = LocalLayoutDirection.current    val viewConfiguration = LocalViewConfiguration.current    ReusableComposeNode<ComposeUiNode, Applier<Any>>(        factory = ComposeUiNode.Constructor,        update = {            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)            set(modifier, ComposeUiNode.SetModifier)        },        content = content    )}
复制代码


  • ReusableComposeNode:创建一个可重用的结构节点。
  • measurePolicy:传递丈量和结构的逻辑。
  • modifier:传递结构的修饰符。
4. Layout 的工作流程

Layout 的工作流程如下:

  • 丈量子组件

    • 调用 measurePolicy.measure 方法,为每个子组件分配空间。

  • 计算结构尺寸

    • 根据子组件的丈量结果,确定结构的总尺寸。

  • 结构子组件

    • 调用 layout 方法,将子组件放置在指定的位置。

5. 自界说结构的示例

以下是一个自界说结构的示例,将子组件垂直排列:
  1. @Composable
  2. fun VerticalLayout(
  3.     modifier: Modifier = Modifier,
  4.     content: @Composable () -> Unit
  5. ) {
  6.     Layout(
  7.         modifier = modifier,
  8.         content = content
  9.     ) { measurables, constraints ->
  10.         // 测量子组件
  11.         val placeables = measurables.map { measurable ->
  12.             measurable.measure(constraints)
  13.         }
  14.         // 计算布局尺寸
  15.         val layoutWidth = placeables.maxOf { it.width }
  16.         val layoutHeight = placeables.sumOf { it.height }
  17.         // 布局子组件
  18.         layout(layoutWidth, layoutHeight) {
  19.             var y = 0
  20.             placeables.forEach { placeable ->
  21.                 placeable.placeRelative(x = 0, y = y)
  22.                 y += placeable.height
  23.             }
  24.         }
  25.     }
  26. }
复制代码
1. Draw 模块的焦点类

1.1 Canvas



  • 作用:Canvas 是 Compose 中的绘制画布,封装了 Android 的 Canvas 类。
  • 源码位置:androidx.compose.ui.graphics.Canvas
  • 关键方法:

    • drawRect:绘制矩形。
    • drawCircle:绘制圆形。
    • drawPath:绘制路径。
    • drawImage:绘制图片。

1.2 Paint



  • 作用:Paint 是 Compose 中的绘制工具,封装了 Android 的 Paint 类。
  • 源码位置:androidx.compose.ui.graphics.Paint
  • 关键属性:

    • color:设置绘制颜色。
    • style:设置绘制样式(添补或描边)。
    • strokeWidth:设置描边宽度。

1.3 DrawScope



  • 作用:DrawScope 是 Compose 中用于界说绘制作用域的接口,提供了绘制的上下文信息(如尺寸、密度等)。
  • 源码位置:androidx.compose.ui.graphics.drawscope.DrawScope
  • 关键属性:

    • size:当前绘制地区的大小。
    • density:当前设备的屏幕密度。

  • 关键方法:

    • drawRect、drawCircle、drawPath 等,与 Canvas 的方法类似。

1.4 DrawModifier



  • 作用:DrawModifier 是一个 Modifier,用于在 UI 组件上添加自界说绘制逻辑。
  • 源码位置:androidx.compose.ui.draw.DrawModifier
  • 关键实现:DrawModifier 通过 draw 方法将自界说绘制逻辑应用到组件上。
2. Draw 模块的工作原理

2.1 绘制流程


  • 创建 Canvas:Compose 在渲染 UI 时,会为每个组件创建一个 Canvas。
  • 调用 DrawScope:通过 DrawScope 提供绘制上下文(如尺寸、密度等)。
  • 执行绘制逻辑:在 DrawScope 中调用 drawRect、drawCircle 等方法完成绘制。
  • 应用 DrawModifier:假如组件使用了 DrawModifier,会调用其 draw 方法应用自界说绘制逻辑。
2.2 重组与绘制



  • 当组件的状态发生变革时,Compose 会触发重组。
  • 在重组过程中,DrawScope 会重新调用绘制逻辑,更新 UI。
3. Draw 模块的关键源码剖析

3.1 Canvas 的实现

Canvas 是对 Android Canvas 的封装,源码位于 androidx.compose.ui.graphics.Canvas。以下是关键方法:
  1. fun drawRect(rect: Rect, paint: Paint) {
  2.     nativeCanvas.drawRect(rect.toAndroidRect(), paint.asFrameworkPaint())
  3. }
复制代码


  • nativeCanvas:底层的 Android Canvas。
  • paint.asFrameworkPaint():将 Compose 的 Paint 转换为 Android 的 Paint。
3.2 DrawScope 的实现

DrawScope 是绘制作用域的接口,源码位于 androidx.compose.ui.graphics.drawscope.DrawScope。以下是关键方法:
  1. fun drawCircle(
  2.     color: Color,
  3.     radius: Float,
  4.     center: Offset,
  5.     style: DrawStyle = Fill,
  6.     alpha: Float = 1.0f,
  7.     colorFilter: ColorFilter? = null,
  8.     blendMode: BlendMode = DefaultBlendMode
  9. ) {
  10.     // 调用 Canvas 的绘制方法
  11.     canvas.drawCircle(center, radius, paint)
  12. }
复制代码


  • canvas:当前的 Canvas。
  • paint:当前的 Paint。
3.3 DrawModifier 的实现

DrawModifier 是用于添加自界说绘制逻辑的 Modifier,源码位于 androidx.compose.ui.draw.DrawModifier。以下是关键实现:
  1. class DrawModifier(
  2.     private val onDraw: DrawScope.() -> Unit
  3. ) : Modifier.Element, DrawModifier {
  4.     override fun ContentDrawScope.draw() {
  5.         onDraw()
  6.     }
  7. }
复制代码


  • onDraw:自界说绘制逻辑。
  • ContentDrawScope:继续自 DrawScope,提供了绘制上下文。
4. Draw 模块的使用示例

4.1 自界说绘制

以下是一个简朴的自界说绘制示例:
  1. @Composable
  2. fun CustomDrawComponent() {
  3.     Canvas(modifier = Modifier.fillMaxSize()) {
  4.         drawRect(color = Color.Red, topLeft = Offset(100f, 100f), size = Size(200f, 200f))
  5.         drawCircle(color = Color.Blue, radius = 100f, center = Offset(300f, 300f))
  6.     }
  7. }
复制代码
4.2 使用 DrawModifier

以下是一个使用 DrawModifier 的示例:
  1. @Composable
  2. fun CustomDrawModifier() {
  3.     Box(
  4.         modifier = Modifier
  5.             .size(200.dp)
  6.             .drawWithContent {
  7.                 drawRect(color = Color.Red, size = size)
  8.                 drawContent()
  9.             }
  10.     ) {
  11.         Text("Hello, Compose!")
  12.     }
  13. }
复制代码
5. Draw 模块的优化

5.1 硬件加速

Compose 的绘制过程默认使用硬件加速,性能高效。
5.2 最小化绘制地区

Compose 会自动优化绘制地区,只绘制需要更新的部门。
5.3 缓存绘制结果

对于复杂的绘制逻辑,可以使用 RenderNode 或 Bitmap 缓存绘制结果,减少重复计算。
4. Compose 的优化技能

4.1 静态分析

Compose 编译器插件通过静态分析优化代码,减少不必要的重组。例如:


  • stable 和 unstable 参数:标志参数的稳固性,避免不必要的重组。
  • remember 和 key:通过缓存和唯一标识优化重组。
4.2 增量更新

Compose 通过比力新旧组件树,只更新变革的部门。例如:


  • DiffUtil:比力组件树的变革,天生最小更新集。
  • LazyColumn 和 LazyRow:通过懒加载优化列表性能。
4.3 GPU 加速

Compose 的绘制过程使用 GPU 加速,提拔渲染性能。例如:


  • Canvas:支持自界说绘制,性能高效。
  • Modifier:通过链式调用优化 UI 组件的属性设置。

写在后面

总的看来,很像ArkTs的更新方式,有一种莫名的熟悉感哈哈~
   If you like this article, it is written by Johnny Deng.
If not, I don’t know who wrote it.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万有斥力

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表