Compose Codelab 学习 - Jetpack Compose 中的状态

Jetpack Compose 中的状态

  • 官方文档:https://developer.android.google.cn/codelabs/jetpack-compose-state?hl=zh-cn#0
1、Compose 函数与状态
  1. 为所有可组合函数提供默认的 Modifier,从而提高可重用性,它应作为第一个可选参数显示在参数列表中,位于所有必需参数之后

  2. Compose 还有 mutableStateOf 的其他变体,例如,mutableIntStateOf、mutableLongStateOf、mutableFloatStateOf、mutableDoubleStateOf

  3. 状态驱动型界面可以避免手动更新视图的复杂性,这也不太容易出错,不会忘记根据新状态更新视图,因为系统会自动执行此过程

2、remember 函数的记忆
kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AppTheme {
                Surface(modifier = Modifier.padding(16.dp)) {
                    WellnessScreen()
                }
            }
        }
    }
}

@Composable
fun WellnessTaskItem(
    taskName: String,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier
            .fillMaxWidth()
            .requiredHeight(72.dp)
            .background(MaterialTheme.colorScheme.primaryContainer),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 16.dp),
            text = taskName,
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
        IconButton(onClick = onClose) {
            Icon(Icons.Filled.Close, contentDescription = "Close")
        }
    }
}

@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        var count by remember { mutableStateOf(0) }
        if (count > 0) {
            var showTask by remember { mutableStateOf(true) }
            if (showTask) {
                WellnessTaskItem(
                    onClose = { showTask = false },
                    taskName = "Have you taken your 15 minute walk today?"
                )
            }
            Text("You've had $count glasses.")
        }
        Row(Modifier.padding(top = 8.dp)) {
            Button(onClick = { count++ }, enabled = count < 10) {
                Text("Add one")
            }
            Button(
                onClick = { count = 0 },
                Modifier.padding(start = 8.dp)
            ) {
                Text("Clear water count")
            }
        }
    }
}

@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
    WaterCounter(modifier = modifier)
}
  1. 初始:count 为 0,if (count > 0) 不成立

    count 值:0

    showTask 值:还没创建

    界面变化:显示【Add one】按钮

  2. 点击【Add one】:count 变成 1,if (count > 0) 成立,创建 showTask,初始 true,显示任务

    count 值:1

    showTask 值:true

    界面变化:显示任务 + 【You've had 1 glasses.】文本

  3. 点击任务上的【X】:onClose 触发,showTask 变成 false,不显示任务

    count 值:1

    showTask 值:false

    界面变化:任务消失,剩下文字

  4. 再点击【Add one】:count 变成 2,if (count > 0) 成立,showTask 还是 false,不显示任务

    count 值:2

    showTask 值:false

    界面变化:任务依然不显示,因为记住了关闭状态

  5. 点击【Clear water count】:count 变成 0,if (count > 0) 不成立,showTask 被遗忘

    count 值:0

    showTask 值:已被遗忘

    界面变化:回到初始状态,显示按钮

  6. 再点击【Add one】:count 变成 1,if (count > 0) 成立,创建 showTask,初始 true,显示任务

    count 值:1

    showTask 值:重新创建,变成 true

    界面变化:任务又出现了

3、配置更改后保持状态
  1. 虽然 remember 可以在重组后保持状态,但不会在配置更改后保持状态,需要使用 rememberSaveable

  2. 配置更改有,更改语言、在深色模式与浅色模式之间切换,执行任何导致 Android 重新创建运行中 activity 的其他配置更改

kotlin 复制代码
var count by rememberSaveable { mutableStateOf(0) }
4、状态提升
(1)基本介绍
  1. 使用 remember 存储对象的可组合函数包含内部状态,这会使该可组合函数有状态

    在调用方不需要控制状态,并且不必自行管理状态便可使用状态的情况下,可组合函数有状态会很有用

    但是,具有内部状态的可组合函数往往不易重复使用,也更难测试

  2. 不保存任何状态的可组合函数称为无状态可组合函数。如需创建无状态可组合函数,一种简单的方法是使用状态提升

  3. Compose 中的状态提升是一种将状态移至可组合函数的调用方以使可组合函数无状态的模式。Compose 中的常规状态提升模式是将状态变量替换为两个参数

koltin 复制代码
// 要显示的当前值

value: T
koltin 复制代码
// 请求更改值的事件,包含一个新值 T

onValueChange: (T) -> Unit
  1. 状态下降、事件上升的这种模式称为单向数据流(UDF),而状态提升就是在 Compose 中实现此架构的方式。以这种方式提升的状态具有一些重要的属性

    1. 单一事实来源:通过移动状态,而不是复制状态,这样可确保只有一个事实来源。这有助于避免 Bug

    2. 可共享:可与多个可组合函数共享提升的状态

    3. 可拦截:无状态可组合函数的调用方可以在更改状态之前决定忽略或修改事件

    4. 分离:无状态可组合函数的状态可以存储在任何位置。例如,存储在 ViewModel 中

  2. 当所有状态都可以从可组合函数中提取出来时,生成的可组合函数称为无状态函数

    1. 无状态可组合函数:不具有任何状态的可组合函数,这意味着它不会存储、定义或修改新状态

    2. 有状态可组合函数:具有可以随时间变化的状态的可组合函数

    在实际应用中,让可组合函数 100% 完全无状态可能很难实现,具体取决于可组合函数的职责

    在设计可组合函数时,应该让可组合函数拥有尽可能少的状态,并能够在必要时通过在可组合函数的 API 中公开状态来提升状态

  3. 提升状态时,有三条规则可帮助开发者弄清楚状态应去向何处

    1. 状态应至少提升到使用该状态(读取)的所有可组合函数的最低共同父项

    2. 状态应至少提升到它可以发生变化(写入)的最高级别

    3. 如果两种状态发生变化以响应相同的事件,它们应提升到同一级别

    可以将状态提升到高于这些规则要求的级别,但如果未将状态提升到足够高的级别,则遵循单向数据流会变得困难或不可能

(2)演示
kotlin 复制代码
@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        if (count > 0) {
            Text("You've had $count glasses.")
        }
        Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
            Text("Add one")
        }
    }
}

@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
    var count by rememberSaveable { mutableStateOf(0) }
    StatelessCounter(count, { count++ }, modifier)
}
5、使用列表
(1)演示
kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AppTheme {
                WellnessScreen()
            }
        }
    }
}

@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        if (count > 0) {
            Text("You've had $count glasses.")
        }
        Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
            Text("Add one")
        }
    }
}

@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
    var count by rememberSaveable { mutableStateOf(0) }
    StatelessCounter(count, { count++ }, modifier)
}

data class WellnessTask(val id: Int, val label: String)

fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }

@Composable
fun WellnessTaskItem(
    taskName: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier, verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 16.dp),
            text = taskName
        )
        // 提升 checked 状态和 onCheckedChange 回调,使函数变为无状态
        Checkbox(
            checked = checked,
            onCheckedChange = onCheckedChange
        )
        IconButton(onClick = onClose) {
            Icon(Icons.Filled.Close, contentDescription = "Close")
        }
    }
}

@Composable
fun WellnessTaskItem(
    taskName: String,
    modifier: Modifier = Modifier
) {
    // 如果不使用 rememberSaveable 函数,会出现如下情况
    // 勾选列表顶部的元素 → 滚动列表,使这些元素位于屏幕之外 → 滚动列表到顶部,之前勾选的列表项处于未选中状态
    var checkedState by rememberSaveable { mutableStateOf(false) }

    WellnessTaskItem(
        taskName = taskName,
        checked = checkedState,
        onCheckedChange = { newValue -> checkedState = newValue },
        onClose = {},
        modifier = modifier,
    )
}

@Composable
fun WellnessTasksList(
    list: List<WellnessTask> = remember { getWellnessTasks() },
    modifier: Modifier = Modifier
) {
    LazyColumn(
        modifier = modifier
    ) {
        items(list) { task ->
            WellnessTaskItem(taskName = task.label)
        }
    }
}

@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
    Column(modifier = modifier) {
        StatefulCounter()
        WellnessTasksList()
    }
}
(2)列表要点补充
  1. LazyColumn 内部默认用 rememberSaveable 保存滚动位置(rememberLazyListState 使用 rememberSaveable 为列表创建初始状态),Activity 重建后自动恢复,不需要额外配置

  2. 如果想手动控制(例如,回到顶部、监听是否滑到底部),就自己创建 LazyListState 通过 state 参数传进去

  3. Compose 很多组件(例如,BottomSheetScaffold)都遵循"提供可选 state 参数,并用 rememberXxxState 给默认值"的模式,方便开箱即用与深度定制

kotlin 复制代码
@Composable
fun LazyColumn(
    modifier: Modifier = Modifier,
    state: LazyListState = rememberLazyListState(),
    contentPadding: PaddingValues = PaddingValues(0.dp),
    reverseLayout: Boolean = false,
    verticalArrangement: Arrangement.Vertical =
        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
    userScrollEnabled: Boolean = true,
    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
    content: LazyListScope.() -> Unit,
)
kotlin 复制代码
@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0,
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset)
    }
}
6、使用可观察的可变列表
(1)基本介绍
  1. 需要创建一个可由 Compose 观察的 MutableList 实例。此结构可允许 Compose 跟踪更改,以便在列表中添加或移除项时重组界面

  2. 扩展函数 toMutableStateList 用于根据初始可变或不可变的集合(例如,List)来创建可观察的 MutableList

    复制一份可变或不可变集合的数据,把它变成可变且可观察的状态容器

  3. 也可以使用 mutableStateListOf 函数来创建可观察的 MutableList,然后为初始状态添加元素

    mutableStateOf 函数会返回一个类型为 MutableState 的对象

    mutableStateListOf 和 toMutableStateList 函数会返回一个类型为 SnapshotStateList 的对象

(2)演示
kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AppTheme {
                WellnessScreen()
            }
        }
    }
}

@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        if (count > 0) {
            Text("You've had $count glasses.")
        }
        Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
            Text("Add one")
        }
    }
}

@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
    var count by rememberSaveable { mutableStateOf(0) }
    StatelessCounter(count, { count++ }, modifier)
}

data class WellnessTask(val id: Int, val label: String)

fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }

@Composable
fun WellnessTaskItem(
    taskName: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier, verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 16.dp),
            text = taskName
        )
        // 提升 checked 状态和 onCheckedChange 回调,使函数变为无状态
        Checkbox(
            checked = checked,
            onCheckedChange = onCheckedChange
        )
        IconButton(onClick = onClose) {
            Icon(Icons.Filled.Close, contentDescription = "Close")
        }
    }
}

@Composable
fun WellnessTaskItem(
    taskName: String,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    // 如果不使用 rememberSaveable 函数,会出现如下情况
    // 勾选列表顶部的元素 → 滚动列表,使这些元素位于屏幕之外 → 滚动列表到顶部,之前勾选的列表项处于未选中状态
    var checkedState by rememberSaveable { mutableStateOf(false) }

    WellnessTaskItem(
        taskName = taskName,
        checked = checkedState,
        onCheckedChange = { newValue -> checkedState = newValue },
        onClose = onClose,
        modifier = modifier,
    )
}

@Composable
fun WellnessTasksList(
    list: List<WellnessTask> = remember { getWellnessTasks() },
    onCloseTask: (WellnessTask) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyColumn(
        modifier = modifier
    ) {
        // items 函数会接收一个 key 参数
        // 默认情况下,每个项的状态均与该项在列表中的位置相对应
        // 在可变列表中,当数据集发生变化时,这会导致问题,因为实际改变位置的项会丢失任何记住的状态
        items(
            items = list,
            key = { task -> task.id }
        ) { task ->
            WellnessTaskItem(taskName = task.label, onClose = { onCloseTask(task) })
        }
    }
}

@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
    Column(modifier = modifier) {
        StatefulCounter()
        val list = remember { getWellnessTasks().toMutableStateList() }
        WellnessTasksList(list, onCloseTask = { task -> list.remove(task) })
    }
}
(3)mutableStateListOf 函数注意事项
  1. 使用 mutableStateListOf 函数创建列表,如果使用不当,则可能会导致意外重组和界面性能欠佳

  2. 如果仅定义列表,然后在不同的操作中添加任务,则会导致系统在每次重组时都添加重复项

kotlin 复制代码
val list = remember { mutableStateListOf<WellnessTask>() }

list.addAll(getWellnessTasks())
复制代码
执行过程

1. 首次打开页面:创建空列表 → 添加 5 条数据 → 界面显示 5 条

2. 发生重组,例如,输入框变化:remember 发现已有列表,不再创建新列表 → 但 addAll 这行又执行了一次 → 又添加了 5 条 → 界面显示 10 条

3. 再次重组:又会再加 5 条 → 变成 15 条
  1. 应当在单一操作中创建包含初始值的列表,然后将其传递给 remember 函数
kotlin 复制代码
val list = remember {
    mutableStateListOf<WellnessTask>().apply { addAll(getWellnessTasks()) }
}
(4)配置更改后保持状态
  1. 如果尝试使用 rememberSaveable 函数将列表存储,则会发生运行时异常

    cannot be saved using the current SaveableStateRegistry.
    The default implementation only supports types which can be stored inside the Bundle.
    Please consider implementing a custom Saver for this class and pass it to rememberSaveable().

    此错误消息指出,需要提供自定义 Saver

    简单来说,就是没法存进 Bundle,只支持 String、Int、Boolean 这些简单类型,不支持自定义类

  2. 其实,不应使用 rememberSaveable 函数来存储需要长时间序列化或反序列化操作的大量数据或复杂数据结构

7、ViewModel 中的状态
(1)基本介绍
  1. 界面状态描述屏幕上显示的内容,例如,任务列表

  2. 应用逻辑描述应用的行为方式以及应如何响应状态变化。逻辑分为两种类型:第一种是界面逻辑,第二种是业务逻辑

    界面逻辑涉及如何在屏幕上显示状态变化。例如,导航逻辑、显示信息提示控件等

    业务逻辑决定如何处理状态更改。例如,付款、存储用户偏好设置等。该逻辑通常位于业务层或数据层,但绝不会位于界面层

  3. ViewModels 提供界面状态以及对位于应用其他层中的业务逻辑的访问。ViewModel 还会在配置更改后继续保留,因此其生命周期比组合更长

  4. ViewModel 可以遵循 Compose 内容(即 activity 或 fragment)的主机的生命周期,也可以遵循导航图的目的地的生命周期(Compose Navigation)

(2)引入 ViewModel
  • libs.versions.toml,因为指定 Compose BOM,所以不需要指定版本号
toml 复制代码
[versions]
composeBom = "2026.02.01"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose" }
  • 项目级 build.gradle.kts
kotlin 复制代码
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.lifecycle.viewmodel.compose)
(3)演示
  • WellnessTask.kt
kotlin 复制代码
data class WellnessTask(val id: Int, val label: String)
  • WellnessViewModel.kt
kotlin 复制代码
class WellnessViewModel : ViewModel() {
    private val _tasks = getWellnessTasks().toMutableStateList()

    val tasks: List<WellnessTask>
        get() = _tasks

    fun remove(item: WellnessTask) {
        _tasks.remove(item)
    }
}

private fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }
  • MainActivity.kt
kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AppTheme {
                WellnessScreen()
            }
        }
    }
}

@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        if (count > 0) {
            Text("You've had $count glasses.")
        }
        Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
            Text("Add one")
        }
    }
}

@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
    var count by rememberSaveable { mutableStateOf(0) }
    StatelessCounter(count, { count++ }, modifier)
}

@Composable
fun WellnessTaskItem(
    taskName: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier, verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 16.dp),
            text = taskName
        )
        // 提升 checked 状态和 onCheckedChange 回调,使函数变为无状态
        Checkbox(
            checked = checked,
            onCheckedChange = onCheckedChange
        )
        IconButton(onClick = onClose) {
            Icon(Icons.Filled.Close, contentDescription = "Close")
        }
    }
}

@Composable
fun WellnessTaskItem(
    taskName: String,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    // 如果不使用 rememberSaveable 函数,会出现如下情况
    // 勾选列表顶部的元素 → 滚动列表,使这些元素位于屏幕之外 → 滚动列表到顶部,之前勾选的列表项处于未选中状态
    var checkedState by rememberSaveable { mutableStateOf(false) }

    WellnessTaskItem(
        taskName = taskName,
        checked = checkedState,
        onCheckedChange = { newValue -> checkedState = newValue },
        onClose = onClose,
        modifier = modifier,
    )
}

@Composable
fun WellnessTasksList(
    list: List<WellnessTask>,
    onCloseTask: (WellnessTask) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyColumn(
        modifier = modifier
    ) {
        // items 函数会接收一个 key 参数
        // 默认情况下,每个项的状态均与该项在列表中的位置相对应
        // 在可变列表中,当数据集发生变化时,这会导致问题,因为实际改变位置的项会丢失任何记住的状态
        items(
            items = list,
            key = { task -> task.id }
        ) { task ->
            WellnessTaskItem(taskName = task.label, onClose = { onCloseTask(task) })
        }
    }
}

@Composable
fun WellnessScreen(
    modifier: Modifier = Modifier,
    wellnessViewModel: WellnessViewModel = viewModel()
) {
    Column(modifier = modifier) {
        StatefulCounter()
        WellnessTasksList(
            wellnessViewModel.tasks,
            onCloseTask = { task -> wellnessViewModel.remove(task) })
    }
}
(4)viewModel 函数
  1. viewModel 函数会返回现有的 ViewModel,或在给定作用域内创建新的 ViewModel。只要 Activity 没销毁,就拿同一个 ViewModel

  2. 配置更改主要指屏幕旋转,旋转屏幕时 Activity 会销毁重建,普通变量会归零,但因为 ViewModel 没销毁,所以数据还在

  3. ViewModel 在任何情况下都不会自动保留应用的状态,如果是系统发起的进程终止的情况,不能只靠 ViewModel

  4. 建议用于屏幕级根组合,绝不应传递给其他函数,即只传数据和回调

    // 错误写法:父组件把整个 ViewModel 传递给子组件

    @Composable
    fun ParentScreen() {
    val vm = viewModel()
    ChildA(vm)
    }

    @Composable
    fun ChildA(vm: MyVM) {
    Text(vm.name)
    }

kotlin 复制代码
// 正确写法:父组件把数据和回调传递给子组件

@Composable
fun ParentScreen() {
    val vm = viewModel<MyVM>()
    ChildA(
        name = vm.name,
        onButtonClick = { vm.updateName() }
    )
}

@Composable
fun ChildA(name: String, onButtonClick: () -> Unit) {
    Text(name)
    Button(onClick = onButtonClick) { ... }
}
(5)选中状态
  • WellnessTask.kt
kotlin 复制代码
data class WellnessTask(
    val id: Int,
    val label: String,
    val initialChecked: Boolean = false
) {
    var checked by mutableStateOf(initialChecked)
}
  • WellnessViewModel.kt
kotlin 复制代码
class WellnessViewModel : ViewModel() {
    private val _tasks = getWellnessTasks().toMutableStateList()
    private val _count = mutableStateOf(0)

    val tasks: List<WellnessTask>
        get() = _tasks

    fun remove(item: WellnessTask) {
        _tasks.remove(item)
    }

    fun changeTaskChecked(item: WellnessTask, checked: Boolean) {
        _tasks.find { it.id == item.id }?.let { task ->
            task.checked = checked
        }
    }

    val count: Int
        get() = _count.value

    fun increment() {
        _count.value++
    }
}

private fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }
  • MainActivity.kt
kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AppTheme {
                WellnessScreen()
            }
        }
    }
}

@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        if (count > 0) {
            Text("You've had $count glasses.")
        }
        Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
            Text("Add one")
        }
    }
}

@Composable
fun WellnessTaskItem(
    taskName: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
    onClose: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier, verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 16.dp),
            text = taskName
        )
        // 提升 checked 状态和 onCheckedChange 回调,使函数变为无状态
        Checkbox(
            checked = checked,
            onCheckedChange = onCheckedChange
        )
        IconButton(onClick = onClose) {
            Icon(Icons.Filled.Close, contentDescription = "Close")
        }
    }
}

@Composable
fun WellnessTasksList(
    list: List<WellnessTask>,
    onCheckedTask: (WellnessTask, Boolean) -> Unit,
    onCloseTask: (WellnessTask) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyColumn(
        modifier = modifier
    ) {
        // items 函数会接收一个 key 参数
        // 默认情况下,每个项的状态均与该项在列表中的位置相对应
        // 在可变列表中,当数据集发生变化时,这会导致问题,因为实际改变位置的项会丢失任何记住的状态
        items(
            items = list,
            key = { task -> task.id }
        ) { task ->
            WellnessTaskItem(
                taskName = task.label,
                checked = task.checked,
                onCheckedChange = { checked -> onCheckedTask(task, checked) },
                onClose = { onCloseTask(task) }
            )
        }
    }
}

@Composable
fun WellnessScreen(
    modifier: Modifier = Modifier,
    wellnessViewModel: WellnessViewModel = viewModel()
) {
    Column(modifier = modifier) {
        StatelessCounter(wellnessViewModel.count, wellnessViewModel::increment)
        WellnessTasksList(
            wellnessViewModel.tasks,
            onCheckedTask = { task, checked ->
                wellnessViewModel.changeTaskChecked(task, checked)
            },
            onCloseTask = { task ->
                wellnessViewModel.remove(task)
            }
        )
    }
}
相关推荐
土司大王1 小时前
LeetCode hot100——全排列
java·算法·leetcode
sbjdhjd1 小时前
在 8 字符、字母数字过滤与 PHP 版本差异下复盘临时文件命令执行链 | (8字符绕过)
android·java·开发语言·安全·web安全·数据挖掘·php
秋饼1 小时前
Spring AI 2.0 多模型路由网关:Astra/Sol 到国产模型的智能调度与降级
java·ai·技术分享·后端开发
Wang's Blog1 小时前
Java框架快速入门: Spring Security+OAuth2之密码存储安全进化与实践
java·安全·spring
小七在进步1 小时前
C++入门(3)
android·java·c++
码农coding1 小时前
android12 SystemUI下拉通知栏的加载流程
android
前端 贾公子1 小时前
Milvus使用指南 (下)
java·服务器·前端
Rain的Java大神之路1 小时前
高并发场景下MySQL数据库性能优化实战
java·后端·mysql
步行cgn2 小时前
OCP开闭原则:面向对象设计的核心原则
java·开发语言·开闭原则