马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
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 组件。
- 例如:
- @Composable
- fun Greeting(name: String) {
- Text(text = "Hello, $name!")
- }
复制代码 1.3 状态管理
- Compose 使用 State 来管理 UI 的状态。当状态发生变革时,Compose 会自动重新绘制相干的 UI。
- 例如:
- @Composable
- fun Counter() {
- val count = remember { mutableStateOf(0) }
- Button(onClick = { count.value++ }) {
- Text("Clicked ${count.value} times")
- }
- }
复制代码 1.4 重组(Recomposition)
- 当状态发生变革时,Compose 会触发重组(Recomposition),重新调用相干的 @Composable 函数来更新 UI。
- Compose 会自动优化重组过程,只更新需要变革的部门。
2. Compose 的根本使用
2.1 设置项目
在 build.gradle 中添加 Compose 的依赖:
- dependencies {
- implementation "androidx.compose.ui:ui:1.3.3"
- implementation "androidx.compose.material:material:1.3.3"
- implementation "androidx.compose.runtime:runtime:1.3.3"
- implementation "androidx.activity:activity-compose:1.6.1"
- }
复制代码 2.2 创建 Composable 函数
界说一个简朴的 UI 组件:
- @Composable
- fun Greeting(name: String) {
- Text(text = "Hello, $name!")
- }
复制代码 2.3 在 Activity 中使用 Compose
在 Activity 中设置 Compose 的内容:
- class MainActivity : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- Greeting(name = "Compose")
- }
- }
- }
复制代码 3. Compose 的高级特性
3.1 结构
Compose 提供了多种结构组件,如 Column、Row、Box 等:
- @Composable
- fun ProfileCard() {
- Column {
- Text("John Doe")
- Text("Software Engineer")
- }
- }
复制代码 3.2 主题
可以通过 MaterialTheme 界说应用的主题:
- @Composable
- fun App() {
- MaterialTheme {
- Greeting(name = "Compose")
- }
- }
复制代码 3.3 动画
Compose 提供了强大的动画支持:
- @Composable
- fun AnimatedButton() {
- val enabled = remember { mutableStateOf(true) }
- Button(onClick = { enabled.value = !enabled.value }) {
- Text(if (enabled.value) "Enabled" else "Disabled")
- }
- }
复制代码 3.4 状态提拔
将状态提拔到父组件中,以实现更机动的状态管理:
- @Composable
- fun Parent() {
- val count = remember { mutableStateOf(0) }
- Counter(count = count.value, onIncrement = { count.value++ })
- }
- @Composable
- fun Counter(count: Int, onIncrement: () -> Unit) {
- Button(onClick = onIncrement) {
- Text("Clicked $count times")
- }
- }
复制代码 4. Compose 的工作原理
4.1 Compose 的架构
- Compose 基于 Kotlin 的编译器插件,将 @Composable 函数转换为高效的 UI 渲染代码。
- Compose 使用 Slot Table 和 Gap Buffer 技能来管理 UI 的状态和更新。
4.2 重组机制
- 当状态发生变革时,Compose 会标志受影响的 @Composable 函数,并重新调用它们。
- Compose 会通过比力前后两次调用的参数,决定是否需要重组。
4.3 状态管理
- Compose 使用 remember 和 mutableStateOf 来保存和更新状态。
- 状态的变革会触发重组,从而更新 UI。
4.4 结构和绘制
- Compose 的结构体系基于 ConstraintLayout 和 Measure 机制,支持机动的 UI 结构。
- 绘制过程使用 GPU 加速,性能高效。
前置学习
Kotlin remember
remember用于在组件的重新组合(Recomposition)过程中保留状态或计算结果,避免不必要的重复计算或初始化。
remember 的作用
remember 的作用是缓存一个值,并在组件的多次重新组合中保持该值稳定,除非它的依赖项发生了变革。它通常用于管理组件的内部状态或缓存昂贵的计算结果。
remember 的语法
remember 的常见用法有两种:
- 不带依赖项的 remember:
- val value = remember { initialValue }
复制代码
- 这里的 initialValue 是一个 lambda 表达式,它会在第一次组合时执行,并将结果缓存。在后续的重新组合中,remember 会直接返回缓存的值,而不会重新执行 lambda。
- 带依赖项的 remember:
- val value = remember(key1, key2, ...) { initialValue }
复制代码
- 这里的 key1、key2 等是依赖项。当这些依赖项发生变革时,remember 会重新执行 lambda 并更新缓存的值;否则,直接返回缓存的值。
remember 的示例
- 缓存状态:
- @Composable
- fun Counter() {
- val count = remember { mutableStateOf(0) } // 缓存一个状态
- Button(onClick = { count.value++ }) {
- Text("Clicked ${count.value} times")
- }
- }
复制代码
- 这里使用 remember 缓存了一个 MutableState,确保在重新组合时不会重新初始化 count。
- 缓存计算结果:
- @Composable
- fun ExpensiveCalculation(input: Int) {
- val result = remember(input) { // 缓存计算结果,依赖 input
- performExpensiveCalculation(input)
- }
- Text("Result: $result")
- }
复制代码
- 这里使用 remember 缓存了一个昂贵的计算结果,只有当 input 发生变革时才会重新计算。
- 缓存对象:
- @Composable
- fun MyComponent() {
- val myObject = remember { MyObject() } // 缓存一个对象
- Text("Object: $myObject")
- }
复制代码
- 这里使用 remember 缓存了一个对象,避免在每次重新组合时重新创建。
remember 的注意事项
- remember 只能在 @Composable 函数中使用,由于它依赖于 Compose 的重组机制。
- remember 缓存的值只在当前组件的生命周期内有用。假如组件被移除或重修,缓存的值会丢失。
- remember 不能用于跨组件的状态共享,假如需要跨组件共享状态,应该使用 rememberSaveable 或 ViewModel。
remember 与 rememberSaveable
rememberSaveable 是 remember 的增强版,它可以在配置更改(如屏幕旋转)或进程重修时保存状态。例如:
- @Composable
- fun MyComponent() {
- val state = rememberSaveable { mutableStateOf(0) }
- Button(onClick = { state.value++ }) {
- Text("Clicked ${state.value} times")
- }
- }
复制代码 Compose添加动画
1. 状态驱动的动画
Compose 的动画是基于状态驱动的。通过监听状态的变革,Compose 会自动在状态之间进行平滑的过渡。
示例:简朴的淡入淡出动画
- @Composable
- fun FadeInOutAnimation() {
- var visible by remember { mutableStateOf(true) } // 状态控制显示/隐藏
- val alpha by animateFloatAsState(
- targetValue = if (visible) 1f else 0f, // 目标透明度
- animationSpec = tween(durationMillis = 1000) // 动画配置
- )
- Column {
- Button(onClick = { visible = !visible }) {
- Text("Toggle Visibility")
- }
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .height(100.dp)
- .background(Color.Blue)
- .alpha(alpha) // 应用透明动画
- )
- }
- }
复制代码
- animateFloatAsState:用于在状态变革时平滑过渡一个 Float 值。
- tween:界说动画的持续时间和缓动效果。
2. 过渡动画
Transition 用于在多个状态之间进行复杂的动画过渡。
示例:切换大小和颜色的动画
- @Composable
- fun TransitionAnimation() {
- var toggled by remember { mutableStateOf(false) }
- val transition = updateTransition(targetState = toggled, label = "Toggle Transition")
- val size by transition.animateDp(
- transitionSpec = { tween(durationMillis = 500) },
- label = "Size Animation",
- targetValueByState = { isToggled -> if (isToggled) 200.dp else 100.dp }
- )
- val color by transition.animateColor(
- transitionSpec = { tween(durationMillis = 500) },
- label = "Color Animation",
- targetValueByState = { isToggled -> if (isToggled) Color.Red else Color.Blue }
- )
- Column {
- Button(onClick = { toggled = !toggled }) {
- Text("Toggle Animation")
- }
- Box(
- modifier = Modifier
- .size(size)
- .background(color)
- )
- }
- }
复制代码
- updateTransition:创建一个过渡动画对象。
- animateDp 和 animateColor:分别在状态之间过渡 Dp 和 Color 值。
3. 无限循环动画
使用 InfiniteTransition 可以创建无限循环的动画。
示例:无限旋转的圆圈
- @Composable
- fun InfiniteRotationAnimation() {
- val infiniteTransition = rememberInfiniteTransition()
- val rotation by infiniteTransition.animateFloat(
- initialValue = 0f,
- targetValue = 360f,
- animationSpec = infiniteRepeatable(
- animation = tween(durationMillis = 1000, easing = LinearEasing),
- repeatMode = RepeatMode.Restart
- )
- )
- Box(
- modifier = Modifier
- .fillMaxSize()
- .wrapContentSize(Alignment.Center)
- ) {
- Box(
- modifier = Modifier
- .size(100.dp)
- .background(Color.Blue)
- .graphicsLayer {
- rotationZ = rotation // 应用旋转动画
- }
- )
- }
- }
复制代码
- infiniteRepeatable:界说一个无限循环的动画。
4. 手势驱动的动画
Compose 支持将动画与手势联合,实现更自然的交互效果。
示例:拖动动画
- @Composable
- fun DraggableBox() {
- var offsetX by remember { mutableStateOf(0f) }
- var offsetY by remember { mutableStateOf(0f) }
- Box(
- modifier = Modifier
- .fillMaxSize()
- .pointerInput(Unit) {
- detectDragGestures { change, dragAmount ->
- offsetX += dragAmount.x
- offsetY += dragAmount.y
- }
- }
- ) {
- Box(
- modifier = Modifier
- .offset { IntOffset(offsetX.toInt(), offsetY.toInt()) }
- .size(100.dp)
- .background(Color.Red)
- )
- }
- }
复制代码
- pointerInput 和 detectDragGestures:用于监听拖动事件。
- offset:根据拖动间隔更新组件的位置。
5. 自界说动画
假如需要更复杂的动画,可以使用 Animatable 或 AnimationState 进行自界说控制。
示例:使用 Animatable 实现弹簧动画
- @Composable
- fun SpringAnimation() {
- val animatable = remember { Animatable(0f, Dp.VectorConverter) }
- LaunchedEffect(Unit) {
- animatable.animateTo(
- targetValue = 200.dp.value,
- animationSpec = spring(
- dampingRatio = Spring.DampingRatioLowBouncy,
- stiffness = Spring.StiffnessLow
- )
- )
- }
- Box(
- modifier = Modifier
- .offset { IntOffset(0, animatable.value.toInt()) }
- .size(100.dp)
- .background(Color.Green)
- )
- }
复制代码
- Animatable:用于创建自界说动画。
- spring:界说弹簧动画的特性。
6. 动画的可见性
Compose 提供了 AnimatedVisibility,可以方便地实现组件的体现和隐藏动画。
示例:滑动体现/隐藏动画
- @Composable
- fun AnimatedVisibilityExample() {
- var visible by remember { mutableStateOf(true) }
- Column {
- Button(onClick = { visible = !visible }) {
- Text("Toggle Visibility")
- }
- AnimatedVisibility(
- visible = visible,
- enter = slideInVertically() + fadeIn(),
- exit = slideOutVertically() + fadeOut()
- ) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .height(100.dp)
- .background(Color.Blue)
- )
- }
- }
- }
复制代码
- 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 函数:
- @Composable
- fun MyComponent() {
- val count = remember { mutableStateOf(0) }
- Text("Count: ${count.value}")
- }
复制代码 在 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 函数转换为以下结构:
- @Composable
- fun Example() {
- // 原始代码
- Text("Hello, Compose!")
- }
- // 编译后
- fun Example(composer: Composer, key: Int) {
- composer.start(key)
- Text(composer, "Hello, Compose!")
- composer.end()
- }
复制代码
- composer:用于管理 UI 组件的状态和条理结构。
- key:用于标识组件的唯一性。
1. Composer 的作用
Composer 的重要职责包罗:
- 管理 Composable 函数的执行:通过调用 Composable 函数,天生 UI 组件树。
- 状态管理:存储和更新 Composable 函数中的状态(如 mutableStateOf)。
- 支持重组(Recomposition):在状态变革时,重新执行受影响的 Composable 函数,并更新 UI。
- 协调结构和绘制:将天生的组件树传递给结构和绘制体系。
2. Composer 的焦点方法
Composer 接口界说了一系列方法,用于管理 Composable 函数的执行和状态存储。以下是关键方法及其作用:
start 和 end
- fun start(key: Int, group: Int)
- fun end()
复制代码
- start:标志一个 Composable 函数的开始,并分配一个唯一的 key 和 group。
- end:标志一个 Composable 函数的结束。
- 这两个方法用于构建组件树的条理结构。
createNode
- fun createNode(factory: () -> T)
复制代码
- 创建一个 UI 节点(如 LayoutNode 或 TextNode),并将其插入组件树。
setValue
- fun setValue(value: Any?)
复制代码
changed
- fun changed(value: Any?): Boolean
复制代码
remember
- fun <T> remember(value: T): T
复制代码
- 将值存储在 Slot Table 中,并在后续执行中返回相同的值(除非依赖项发生变革)。
updateScope
- 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 函数
- @Composable
- fun MyComponent() {
- val count = remember { mutableStateOf(0) }
- Text("Count: ${count.value}")
- }
复制代码 执行流程
- 调用 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 的焦点字段包罗:
- class SlotTable {
- private val slots: IntArray // 存储 Slot 数据
- private val groups: IntArray // 存储 Group 元数据
- private var groupsSize: Int // Group 的数量
- private var slotsSize: Int // Slot 的数量
- }
复制代码
- 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
- fun insertGroup(key: Int, parent: Int, dataStart: Int, dataEnd: Int): Int
复制代码
- 插入一个新的 Group,并返回其索引。
- 更新 groups 和 slots 数组,确保数据的一致性。
删除 Group
- fun removeGroup(group: Int)
复制代码
- 删除指定的 Group。
- 更新 groups 和 slots 数组,回收资源。
更新 Slot 数据
- fun setSlot(index: Int, value: Any?)
复制代码
- 更新 slots 数组中指定索引的值。
- 假如值发生变革,标志 Group 为需要重组。
查找 Group
- 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 函数
- @Composable
- fun MyComponent() {
- val count = remember { mutableStateOf(0) }
- Text("Count: ${count.value}")
- }
复制代码 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 数组:
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。
- class Recomposer {
- private val choreographer = Choreographer.getInstance()
- private val executor = Executors.newSingleThreadExecutor()
- init {
- choreographer.postFrameCallback(frameCallback)
- }
- }
复制代码 帧回调
Recomposer 使用 Choreographer.FrameCallback 来协调帧更新。
- private val frameCallback = object : Choreographer.FrameCallback {
- override fun doFrame(frameTimeNanos: Long) {
- // 执行待处理的重组任务
- executePendingRecompositions()
- // 注册下一帧的回调
- choreographer.postFrameCallback(this)
- }
- }
复制代码 状态监听
Recomposer 监听状态的变革,并标志受影响的 Composable 函数为“脏数据”。
- fun onStateChanged(state: State<*>) {
- // 查找依赖该状态的 Composable 函数
- val affected = findAffectedComposables(state)
- // 标记为需要重组
- markDirty(affected)
- }
复制代码 重组调度
Recomposer 使用线程池执行重组任务。
- private fun executePendingRecompositions() {
- executor.execute {
- // 获取待处理的重组任务
- val tasks = getPendingRecompositionTasks()
- // 执行任务
- tasks.forEach { it.run() }
- }
- }
复制代码 并发重组
Recomposer 支持在多个线程中执行 Composable 函数。
- fun recompose(composable: @Composable () -> Unit) {
- executor.execute {
- // 在后台线程中执行 Composable 函数
- composable()
- }
- }
复制代码 4. Recomposer 的工作流程
- 初始化:
- 创建线程池,并注册到 Choreographer。
- 监听状态变革:
- 当状态发生变革时,标志受影响的 Composable 函数为“脏数据”。
- 调度重组:
- 执行 Composable 函数:
- 在后台线程中执行 Composable 函数,并更新 Slot Table。
- 帧更新:
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 的典型用法如下:
- @Composable
- fun MyCustomLayout(
- modifier: Modifier = Modifier,
- content: @Composable () -> Unit
- ) {
- Layout(
- modifier = modifier,
- content = content
- ) { measurables, constraints ->
- // 测量子组件
- val placeables = measurables.map { measurable ->
- measurable.measure(constraints)
- }
- // 计算布局尺寸
- val layoutWidth = placeables.maxOf { it.width }
- val layoutHeight = placeables.maxOf { it.height }
- // 布局子组件
- layout(layoutWidth, layoutHeight) {
- placeables.forEach { placeable ->
- placeable.placeRelative(x = 0, y = 0) // 自定义位置
- }
- }
- }
- }
复制代码 3. Layout 的源码分析
Layout 是 Compose 中的一个 Composable 函数,其源码位于 androidx.compose.ui.Layout.kt 中。以下是其焦点实现:
Layout 函数签名
- @Composable
- fun Layout(
- modifier: Modifier = Modifier,
- content: @Composable () -> Unit,
- measurePolicy: MeasurePolicy
- )
复制代码
- modifier:用于调整结构的行为和外貌。
- content:子组件的 Composable 内容。
- measurePolicy:界说丈量和结构的逻辑。
MeasurePolicy
MeasurePolicy 是一个接口,界说了如何丈量和结构子组件。它的焦点方法包罗:
- interface MeasurePolicy {
- fun MeasureScope.measure(
- measurables: List<Measurable>,
- constraints: Constraints
- ): MeasureResult
- fun IntrinsicMeasureScope.minIntrinsicWidth(
- measurables: List<IntrinsicMeasurable>,
- height: Int
- ): Int
- fun IntrinsicMeasureScope.minIntrinsicHeight(
- measurables: List<IntrinsicMeasurable>,
- width: Int
- ): Int
- fun IntrinsicMeasureScope.maxIntrinsicWidth(
- measurables: List<IntrinsicMeasurable>,
- height: Int
- ): Int
- fun IntrinsicMeasureScope.maxIntrinsicHeight(
- measurables: List<IntrinsicMeasurable>,
- width: Int
- ): Int
- }
复制代码 最重要的方法是 measure,它负责丈量子组件并返回结构结果。
Layout 的实现
Layout 的实现如下:
- @Composable
- fun Layout(
- modifier: Modifier = Modifier,
- content: @Composable () -> Unit,
- measurePolicy: MeasurePolicy
- )
- { 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. 自界说结构的示例
以下是一个自界说结构的示例,将子组件垂直排列:
- @Composable
- fun VerticalLayout(
- modifier: Modifier = Modifier,
- content: @Composable () -> Unit
- ) {
- Layout(
- modifier = modifier,
- content = content
- ) { measurables, constraints ->
- // 测量子组件
- val placeables = measurables.map { measurable ->
- measurable.measure(constraints)
- }
- // 计算布局尺寸
- val layoutWidth = placeables.maxOf { it.width }
- val layoutHeight = placeables.sumOf { it.height }
- // 布局子组件
- layout(layoutWidth, layoutHeight) {
- var y = 0
- placeables.forEach { placeable ->
- placeable.placeRelative(x = 0, y = y)
- y += placeable.height
- }
- }
- }
- }
复制代码 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。以下是关键方法:
- fun drawRect(rect: Rect, paint: Paint) {
- nativeCanvas.drawRect(rect.toAndroidRect(), paint.asFrameworkPaint())
- }
复制代码
- nativeCanvas:底层的 Android Canvas。
- paint.asFrameworkPaint():将 Compose 的 Paint 转换为 Android 的 Paint。
3.2 DrawScope 的实现
DrawScope 是绘制作用域的接口,源码位于 androidx.compose.ui.graphics.drawscope.DrawScope。以下是关键方法:
- fun drawCircle(
- color: Color,
- radius: Float,
- center: Offset,
- style: DrawStyle = Fill,
- alpha: Float = 1.0f,
- colorFilter: ColorFilter? = null,
- blendMode: BlendMode = DefaultBlendMode
- ) {
- // 调用 Canvas 的绘制方法
- canvas.drawCircle(center, radius, paint)
- }
复制代码
- canvas:当前的 Canvas。
- paint:当前的 Paint。
3.3 DrawModifier 的实现
DrawModifier 是用于添加自界说绘制逻辑的 Modifier,源码位于 androidx.compose.ui.draw.DrawModifier。以下是关键实现:
- class DrawModifier(
- private val onDraw: DrawScope.() -> Unit
- ) : Modifier.Element, DrawModifier {
- override fun ContentDrawScope.draw() {
- onDraw()
- }
- }
复制代码
- onDraw:自界说绘制逻辑。
- ContentDrawScope:继续自 DrawScope,提供了绘制上下文。
4. Draw 模块的使用示例
4.1 自界说绘制
以下是一个简朴的自界说绘制示例:
- @Composable
- fun CustomDrawComponent() {
- Canvas(modifier = Modifier.fillMaxSize()) {
- drawRect(color = Color.Red, topLeft = Offset(100f, 100f), size = Size(200f, 200f))
- drawCircle(color = Color.Blue, radius = 100f, center = Offset(300f, 300f))
- }
- }
复制代码 4.2 使用 DrawModifier
以下是一个使用 DrawModifier 的示例:
- @Composable
- fun CustomDrawModifier() {
- Box(
- modifier = Modifier
- .size(200.dp)
- .drawWithContent {
- drawRect(color = Color.Red, size = size)
- drawContent()
- }
- ) {
- Text("Hello, Compose!")
- }
- }
复制代码 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企服之家,中国第一个企服评测及商务社交产业平台。 |