一、核心概念:先搞懂「状态」的本质
在 Jetpack Compose 中,状态(State) 是实现「界面响应式更新」的核心 ------ 当状态变化时,依赖该状态的组件会自动重组(Recomposition),界面随之更新。
1. 状态的定义
Compose 中的「状态」是可被观察的可变数据,满足两个条件:
- 数据可修改(如
var变量); - 数据变化能被 Compose 感知,并触发依赖组件的重组。
2. 核心原则:「单向数据流」
Compose 推荐遵循 "状态向上提升,事件向下传递" 的原则:
- 状态(State):存储在父组件 / ViewModel 中(单一数据源);
- 事件(Event):子组件通过回调将事件传递给父组件,由父组件修改状态;
- 界面(UI):依赖状态渲染,状态变化自动更新 UI。
二、Compose 中的状态分类(按「存储 / 使用方式」划分)
Compose 提供了多类状态 API,适配不同场景,核心可分为 基础状态、衍生状态、容器状态、生命周期绑定状态 四大类,以下是全量分类及用法:
| 状态类型 | 核心 API | 作用 | 适用场景 |
|---|---|---|---|
| 基础可观察状态 | mutableStateOf/State |
最基础的可观察状态,单个值的响应式存储 | 组件内简单状态(如开关、计数) |
| 衍生状态 | derivedStateOf |
基于现有状态计算出的新状态,仅当依赖的原状态变化时更新 | 复杂计算的状态(如列表筛选、滚动阈值) |
| 集合类型状态 | mutableStateListOf/mutableStateMapOf |
可观察的集合(List/Map),集合元素变化可触发重组 | 动态列表、多选状态等 |
| 生命周期绑定状态 | remember/rememberSaveable |
缓存状态,避免重组 / 配置变更(如横竖屏)丢失 | 组件内临时状态 / 需持久化的状态 |
| 协程绑定状态 | produceState |
从挂起函数(如网络请求)中生成状态,自动处理协程生命周期 | 异步数据加载(网络 / 数据库) |
| 组合状态(多字段) | StateHolder/data class + State |
封装多个状态字段,统一管理 | 复杂组件(如表单:用户名 + 密码 + 勾选状态) |
| 跨组件共享状态 | rememberCoroutineScope/ViewModel |
跨组件 / 跨生命周期共享状态 | 页面级状态、跨组件通信 |
三、各类状态的详细使用方法
1. 基础可观察状态:mutableStateOf(最常用)
mutableStateOf 是 Compose 最核心的状态 API,返回 MutableState<T> 类型,包含 value 属性(可读写)。
核心用法:
kotlin
@Composable
fun BasicStateDemo() {
// 1. 定义可观察状态(remember 缓存,避免重组时重新创建)
var count by remember { mutableStateOf(0) } // 委托语法(推荐)
// 等价写法:val countState = remember { mutableStateOf(0) },使用时 countState.value
Column(modifier = Modifier.padding(16.dp)) {
// 2. 依赖状态渲染 UI
Text(text = "计数:$count")
// 3. 修改状态(触发重组)
Button(onClick = { count++ }) {
Text("点击+1")
}
}
}
关键说明:
by是 Kotlin 委托语法,简化countState.value为count(需导入import androidx.compose.runtime.getValue/setValue);mutableStateOf必须配合remember使用,否则每次重组都会重新创建状态,导致数据丢失。
2. 衍生状态:derivedStateOf
基于现有状态计算新状态,仅当依赖的原状态变化时才重新计算,避免无意义的重复计算。
核心用法(列表筛选示例):
kotlin
@Composable
fun DerivedStateDemo() {
// 原始状态:搜索关键词
var searchText by remember { mutableStateOf("") }
// 原始状态:数据源
val dataList = remember { listOf("Apple", "Banana", "Orange", "Grape") }
// 衍生状态:筛选后的列表(仅 searchText 变化时重新计算)
val filteredList by remember {
derivedStateOf {
dataList.filter { it.contains(searchText, ignoreCase = true) }
}
}
Column(modifier = Modifier.padding(16.dp)) {
TextField(value = searchText, onValueChange = { searchText = it }, label = { Text("搜索") })
// 依赖衍生状态渲染
LazyColumn {
items(filteredList) { item ->
Text(text = item, modifier = Modifier.padding(8.dp))
}
}
}
}
关键说明:
derivedStateOf必须配合remember使用;- 衍生状态是「惰性计算」的:只有当 UI 依赖它时,才会计算值;依赖的原状态不变时,直接返回缓存值。
3. 集合类型状态:mutableStateListOf/mutableStateMapOf
普通的 MutableList/MutableMap 变化无法触发重组,需使用 Compose 封装的可观察集合:
kotlin
@Composable
fun CollectionStateDemo() {
// 可观察列表(元素变化触发重组)
val selectedItems by remember {
mutableStateListOf<String>().apply {
add("Item 1")
add("Item 2")
}
}
Column(modifier = Modifier.padding(16.dp)) {
// 渲染列表
selectedItems.forEach { item ->
Text(text = item, modifier = Modifier.padding(4.dp))
}
// 添加元素(触发重组)
Button(onClick = { selectedItems.add("Item ${selectedItems.size + 1}") }) {
Text("添加元素")
}
}
}
关键说明:
mutableStateListOf等价于ArrayList,但支持元素变化的监听;- 注意:集合本身的引用不变(如
selectedItems = mutableStateListOf())才会触发重组,仅修改元素(add/remove)也会触发。
4. 生命周期绑定状态:remember vs rememberSaveable
两者都是「缓存状态」的 API,但生命周期不同:
| API | 生命周期 | 适用场景 |
|---|---|---|
remember |
组件重组时保留,配置变更(横竖屏 / 切语言)丢失 | 临时状态(如弹窗显隐、输入框临时文本) |
rememberSaveable |
组件重组 + 配置变更都保留,进程销毁丢失 | 需持久化的临时状态(如表单输入、筛选条件) |
用法示例:
scss
@Composable
fun RememberStateDemo() {
// 1. remember:横竖屏切换后丢失
var tempCount by remember { mutableStateOf(0) }
// 2. rememberSaveable:横竖屏切换后保留
var saveCount by rememberSaveable { mutableStateOf(0) }
Column(modifier = Modifier.padding(16.dp)) {
Text("临时计数(横竖屏丢失):$tempCount")
Button(onClick = { tempCount++ }) { Text("+1") }
Spacer(modifier = Modifier.height(16.dp))
Text("可保存计数(横竖屏保留):$saveCount")
Button(onClick = { saveCount++ }) { Text("+1") }
}
}
进阶:rememberSaveable 序列化
如果状态类型是自定义 data class,需实现序列化才能用 rememberSaveable:
kotlin
// 方式1:实现 Parcelable
@Parcelize
data class User(val name: String, val age: Int) : Parcelable
// 方式2:自定义 Saver
val UserSaver = listSaver<User, Any>(
save = { listOf(it.name, it.age) },
restore = { User(it[0] as String, it[1] as Int) }
)
// 使用
var user by rememberSaveable(stateSaver = UserSaver) {
mutableStateOf(User("Tom", 20))
}
5. 协程绑定状态:produceState
从异步操作(如网络请求、数据库查询)中生成状态,自动管理协程生命周期(组件销毁时取消协程):
kotlin
// 模拟网络请求
suspend fun fetchData(): String {
delay(1000)
return "网络请求结果"
}
@Composable
fun ProduceStateDemo() {
// 从挂起函数生成状态
val dataState by produceState(
initialValue = "加载中...", // 初始值
producer = {
// 执行异步操作
val data = fetchData()
// 更新状态(setValue 是 produceState 的内置方法)
value = data
}
)
Text(text = dataState, modifier = Modifier.padding(16.dp))
}
6. 组合状态:封装多字段状态
当组件有多个状态字段时(如表单),推荐封装为「组合状态」,避免零散的 mutableStateOf:
方式 1:自定义 StateHolder 类
ini
// 封装表单状态
class LoginState {
val username = mutableStateOf("")
val password = mutableStateOf("")
val isAgreed = mutableStateOf(false)
// 衍生状态:判断是否可提交
val canSubmit by derivedStateOf {
username.value.isNotEmpty() && password.value.isNotEmpty() && isAgreed.value
}
}
@Composable
fun LoginFormDemo() {
// 缓存 StateHolder
val loginState = remember { LoginState() }
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = loginState.username.value,
onValueChange = { loginState.username.value = it },
label = { Text("用户名") }
)
TextField(
value = loginState.password.value,
onValueChange = { loginState.password.value = it },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation()
)
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = loginState.isAgreed.value,
onCheckedChange = { loginState.isAgreed.value = it }
)
Text("同意协议")
}
// 依赖衍生状态控制按钮启用
Button(
onClick = { /* 提交 */ },
enabled = loginState.canSubmit
) {
Text("提交")
}
}
}
方式 2:结合 ViewModel(页面级状态)
复杂页面的状态推荐放在 ViewModel 中,脱离组件生命周期:
kotlin
// ViewModel 存储状态
class MainViewModel : ViewModel() {
val count = mutableStateOf(0)
// 业务逻辑:修改状态
fun increment() {
count.value++
}
}
@Composable
fun ViewModelStateDemo() {
// 获取 ViewModel
val viewModel: MainViewModel = viewModel()
// 依赖 ViewModel 中的状态
val count = viewModel.count.value
Column(modifier = Modifier.padding(16.dp)) {
Text("计数:$count")
Button(onClick = { viewModel.increment() }) {
Text("+1")
}
}
}
四、状态使用的最佳实践
1. 状态提升(State Hoisting)
将状态存储在「所有依赖它的组件的最近公共父组件」中,避免状态冗余:
kotlin
// 子组件:无状态,仅接收状态和回调
@Composable
fun CounterButton(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("计数:$count")
}
}
// 父组件:存储状态,传递给子组件
@Composable
fun ParentComponent() {
var count by remember { mutableStateOf(0) }
CounterButton(count = count, onIncrement = { count++ })
}
2. 避免「状态下沉」
不要在子组件中存储状态,否则会导致:
- 状态无法共享;
- 组件复用性差;
- 重组范围不可控。
3. 最小化重组范围
- 拆分组件:将依赖不同状态的 UI 拆分为独立组件,避免一个状态变化导致整个页面重组;
- 缓存计算结果:用
derivedStateOf缓存复杂计算,避免每次重组重复计算; - 避免在重组中创建对象:用
remember缓存TextStyle/Painter等对象。
4. 状态与生命周期匹配
| 状态类型 | 生命周期选择 |
|---|---|
| 组件内临时状态 | remember |
| 需保留配置变更的状态 | rememberSaveable |
| 页面级 / 跨组件状态 | ViewModel |
| 应用级全局状态 | DataStore/SharedPreferences + StateFlow |
五、常见坑点与解决方案
| 坑点 | 原因 | 解决方案 |
|---|---|---|
| 状态变化但 UI 不更新 | 1. 用了普通 var 而非 mutableStateOf;2. 状态未用 remember 缓存 |
1. 替换为 var x by remember { mutableStateOf(xxx) };2. 确保状态被 remember 包裹 |
rememberSaveable 报错 "无法序列化" |
状态类型未实现序列化(如自定义类) | 1. 实现 Parcelable/Serializable;2. 自定义 Saver |
| 衍生状态频繁计算 | 依赖的原状态变化过于频繁 | 用 derivedStateOf 替代直接计算,仅当依赖变化时更新 |
| 重组时重复执行异步操作 | 异步操作未放在 LaunchedEffect/produceState 中 |
将异步操作包裹在 LaunchedEffect(key) { ... } 中,通过 key 控制执行时机 |
总结
- Compose 中的状态核心分为 基础状态(mutableStateOf)、衍生状态(derivedStateOf)、集合状态(mutableStateListOf)、生命周期绑定状态(remember/rememberSaveable)、协程状态(produceState) 五类,适配不同场景;
- 状态使用遵循「单向数据流 + 状态提升」原则,组件内临时状态用
remember,需保留配置变更的用rememberSaveable,页面级状态用ViewModel; - 最佳实践:拆分组件最小化重组范围、缓存计算结果、避免状态下沉,确保状态生命周期与使用场景匹配。
简单来说:Compose 中所有能触发 UI 更新的可变数据,都是「可观察状态」,核心是用 mutableStateOf定义、remember 缓存、ViewModel 共享,遵循单向数据流即可高效使用。