使用 State
和 MutableState
让 Compose
能够观察到状态。
Compose
会跟踪每个使用了 State.value
的可组合函数,并在其 value
发生变更时出发重组。
创建 State
:
mutableStateOf(T)
// 对基础类型进行了优化的初始化方法
mutableIntStateOf(0)
mutableLongStateOf(0)
mutableFloatStateOf(0.0)
mutableDoubltStateOf(0.0)
例:
Column {
val count: MutableState<Int> = mutableIntStateOf(0)
Text("Count: ${count.value}")
Button(onClick = { count.value++ }) { Text("Add") }
}
当 count
发生变更时会触发重组,但变量 count
每次都会初始化为 0,所以观察不出变化,需要使用 remember
保留此值。
remember
可组合内嵌函数,系统会在初始组合期间将 remember
计算的值存储在组合中,并在重组期间一直保持存储的值。
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
例:
Column {
val count: MutableState<Int> = remember { mutableStateOf(0) }
Text("Count: ${count.value}")
Button(onClick = { count.value++ }) { Text("Add") }
}
此时计数器已可正常使用并显示了。
remember {
// do once code
}
// remember 里的语句仅会执行一次
remember(userId) {
// refresh profile
}
// 当 remember 的参数 userId 发生改变时,会重新执行 remember 里的语句
Compose
中保存一个轻量级的引用,如唯一标识或键值等,在需要时从外部源(如:数据库或 ViewModel
)获取完整对象。remember
的键值参数,保持仅在参数变化时对象才会被重新创建,避免不必要的对象创建和回收by
委托属性,即将一个对象的属性委托给另一个对象。在该场景下可通过 by
将 State
的属性委托给另一个对象。如:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Column {
val count by remember { mutableStateOf(0) }
Text("Count: ${count}")
Button(onClick = { count++ }) { Text("Add") }
}
通过 by
将 MutableStateOf<Int>
的 value
属性委托给了 count
,可以直接通过 count
对 value
进行增删改查。
remember
会在 activity 重新创建后忘记状态,如:旋转屏幕、更改语言、切换 light / dark 模式等。这种场景下还想保持状态则需要用到 rememberSaveable
,rememberSaveable
会自动保存可保存在 Bundle
中的任何值。(其他值需要转换成 Saver 对象)。
Column {
val count by rememberSaveable { mutableStateOf(0) }
Text("Count: ${count}")
Button(onClick = { count++ }) { Text("Add") }
}
将状态外移至可组合项的调用方,使可组合项变成无状态
的模式。
Tips:在设计可组合函数时,您应该让可组合函数拥有尽可能少的状态
常用的状态提升模式是将状态变量
替换为两个参数:
value: T
:当前值onValueChange: (T) -> Unit
:请求更改值
例如:fun StatelessCounter(count: Int, onIncrement: () -> Unit) {
Column {
Text("Count: ${count}")
Button(onClick = onIncrement) { Text("Add") }
}
}
Tips:仅传递可组合函数所需的状态,以避免不必要的重组并提高可重用性。
最好将状态和逻辑迁移到 viewModel
中,跟页面进行分离,使用 viewModel
统一管理状态,有以下优势:
import androidx.lifecycle.ViewModel
class CounterViewModel: ViewModel() {
private val _count = mutableOfState(0)
val count: MutableOfState<Int>
get() = _count
fun add() {
_count++
}
fun minus() {
_count--
}
}
可以通过 viewModel()
函数,从任何可组合函数访问此 ViewModel
需在 app/build.gradle.kts
文件添加依赖:
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:{2.8.4}")
最新版本见:Lifecycle
使用如下:
fun StatelessCounter(
viewModel: CounterViewModel = viewModel()
) {
Column {
Text("Count: ${viewModel.count}")
Button(onClick = viewModel.add()) { Text("Add") }
Button(onClick = viewModel.minus()) { Text("Minus") }
}
}
列表式数据使用 toMutableStateList
将列表数据整体转换为可观测状态。将数据和业务逻辑都收敛到 ViewModel
里:
import androidx.compose.runtime.toMutableStateList
class Task(
val id: Int,
val count: Int,
initialChecked: Boolean = false
) {
var checked by mutableStateOf(initialChecked)
}
fun getTasks() = List(30) { i -> Task(i) }
class TaskViewModel: ViewModel() {
// 数据,外部仅可读
private val _tasks = getTasks().toMutableStateList()
val tasks: List<Task>
get() = _tasks
fun remove(item: Task) {
_tasks.remove(item)
}
// 业务逻辑
fun changeTaskChecked( item: Task, checked: Boolean) {
_tasks.find { it.id == item.id }?.let { task ->
task.checked = checked
}
}
}
列表式数据UI:
fun TasksList(
viewModel: TaskViewModel = viewModel()
) {
LazyColumn(state = rememberLazyListState()) {
items(
items = viewModel.tasks,
key = { task -> task.id }
) { task ->
Row {
Text("Task: ${task.count}")
Checkbox(
checked = task.checked,
onCheckedChange = { checked ->
viewModel.changeTaskChecked(task, checked)
}
)
}
}
}
}
Reference: State in Jetpack Compose State and Jetpack Compose ViewModel Restoring state in Compose Android Compose remember() 及 重組作用域