Android Compose 状态:核心api,状态恢复,状态提升,状态容器

文章目录

状态与 Compose 的核心概念

  1. 什么是状态?

    在应用中,状态是指任何可以随时间变化的值。范围非常广,大到 Room 数据库的数据,小到类的变量(如:网络连接提示、展开/收起的面板、输入框中的文字)。

  2. 状态与组合 (Composition) 的关系

    声明式 UI:Compose 是声明式的,更新界面的唯一方法是使用新的参数调用同一可组合项(Composable 函数)。

    重组 (Recomposition):当界面的状态发生变化时,Compose 会触发重组,使用新状态重新执行对应的可组合函数来更新 UI。

    必须明确感知:普通的 Kotlin 变量被修改时不会触发 Compose 更新。可组合项必须被显式告知状态的改变(例如使用 MutableState),才能触发重组。


声明和存储状态的核心 API

  1. 创建一个可被 Compose 观察的 MutableState。当它的值发生改变时,Compose 会自动安排读取了该状态的可组合项进行重组。
  2. 单纯使用 mutableStateOf 每次重组都会重新初始化状态。为了让状态在重组中"存活"下来,必须使用 remember。
    在可组合项中声明 MutableState 对象的方法有三种:
kotlin 复制代码
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
// 解构成两个变量,value是值, setValue 是 函数变量, 即 setValue(value)
val (value, setValue) = remember { mutableStateOf(default) }

配置更改后保持、恢复状态

remember 只能在重组时保持状态。如果要在重新创建 activity 或进程之后保留状态。需使用 rememberSaveable

  • 自动保存类型:只要是能存入 Android Bundle 的数据类型,它都能自动保存。
kotlin 复制代码
@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}
  • 如果需要用 rememberSaveable 保存无法直接放入 Bundle 的自定义对象。那可以放入实现了 Parcelable 序列化的对象
kotlin 复制代码
// 1. 添加 @Parcelize 注解并实现 Parcelable
@Parcelize
data class UserState(val name: String, val age: Int) : Parcelable

@Composable
fun ParcelableExample() {
    // 2. 直接使用 rememberSaveable
    var user by rememberSaveable { mutableStateOf(UserState("张三", 18)) }

    Button(onClick = { user = user.copy(age = user.age + 1) }) {
        Text("姓名: ${user.name}, 年龄: ${user.age} (点击长大)")
    }
}
  • 保存无法修改源码的第三方类(使用自定义 Saver)

如果对象无法实现 Parcelable(例如它是第三方库中的类),可使用 mapSaver 或 listSaver 自定义保存和恢复的逻辑。

mapSaver 保存多个属性的示例

kotlin 复制代码
import androidx.compose.runtime.saveable.mapSaver

// 假设这是一个第三方类,有很多属性
data class ComplexUser(
    val id: Long,
    val name: String,
    val age: Int,
    val isVip: Boolean,
    val avatarUrl: String
)

val ComplexUserMapSaver = run {
    // 提前定义好所有的 key
    val idKey = "id"
    val nameKey = "name"
    val ageKey = "age"
    val vipKey = "vip"
    val avatarKey = "avatar"

    mapSaver(
        save = { user ->
            // 把所有需要保存的属性都塞进 map 里
            mapOf(
                idKey to user.id,
                nameKey to user.name,
                ageKey to user.age,
                vipKey to user.isVip,
                avatarKey to user.avatarUrl
            )
        },
        restore = { map ->
            // 从 map 里把所有的属性取出来,重新构建对象
            ComplexUser(
                id = map[idKey] as Long,
                name = map[nameKey] as String,
                age = map[ageKey] as Int,
                isVip = map[vipKey] as Boolean,
                avatarUrl = map[avatarKey] as String
            )
        }
    )
}

listSaver 保存多个属性的示例

不需要定义 Key,只需要保证存进去的顺序和取出来的顺序完全一致即可

kotlin 复制代码
import androidx.compose.runtime.saveable.listSaver

val ComplexUserListSaver = listSaver<ComplexUser, Any>(
    save = { user ->
        // 按照顺序把属性放进 List 里
        listOf(user.id, user.name, user.age, user.isVip, user.avatarUrl)
    },
    restore = { list ->
        // 严格按照上面 save 的索引顺序取出来
        ComplexUser(
            id = list[0] as Long,
            name = list[1] as String,
            age = list[2] as Int,
            isVip = list[3] as Boolean,
            avatarUrl = list[4] as String
        )
    }
)

其他受支持的可被compose观察的状态类型

MutableState<T> 外,对以下这些数据类型,提供了扩展函数,以转换成可被观察的 State<T>

  • Flow 数据, collectAsStateWithLifecycle() 以生命周期感知型方式从 Flow 收集值
  • Flow:collectAsState(),不感知生命周期
  • LiveData: observeAsState()
  • RxJava2: subscribeAsState()
  • RxJava3: subscribeAsState()

这些扩展函数,需要特定的依赖lib

implementation("androidx.lifecycle:lifecycle-runtime-compose")

implementation("androidx.compose.runtime:runtime-livedata")

implementation("androidx.compose.runtime:runtime-rxjava2")

implementation("androidx.compose.runtime:runtime-rxjava3")

还可以对其它任何可观察的类、自定义的可观察类,使用 produceState API 对其进行转换,以生成 State<T>


有状态、无状态与状态提升

有状态 vs 无状态

  • 有状态:在内部使用 remember 创建并管理自己状态的可组合项。调用方不需要关心它的状态,但缺点是不易复用、更难测试。

  • 无状态:不包含任何内部状态,状态完全由外部调用方通过参数传入。

状态提升

这是 Compose 中的核心设计模式,将有状态组件变为无状态组件

状态未提升的示例:

kotlin 复制代码
@Composable
fun HelloContent() {
	var name by rememberSaveable { mutableStateOf("") }
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") })
    }
}

状态提升 的常规实现是将内部状态变量替换为两个参数:

value: T:当前应该显示的值。

onValueChange: (T) -> Unit:请求改变值的回调事件(T 是新值)。

针对上面的示例,增加一个父可组合项 HelloScreen,将 name 状态 ,提升到 HelloScreen 中,状态改变的回调事件,也提升到父项中传入:
状态提升示例:

kotlin 复制代码
@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
    }
}

优势:状态提升后,HelloContent 将变得可复用

并不总是需要提升状态。当其他可组合项不需要控制状态时,可以将状态保留在可组合项内部。


状态容器

状态容器 负责管理可组合函数的状态和逻辑 。它的核心作用是关注点分离,将复杂的状态管理和行为逻辑从 UI 层抽离出来,让可组合函数保持纯粹、简洁,仅专注于如何渲染 UI。

随着 UI 的复杂度增加,如果将所有的状态(如展开/收起状态、滚动位置)和逻辑(如网络请求、数据校验)都写在 Composable 内部,代码会变得极其臃肿且难以测试。状态容器就是为了解决这个问题而存在的。

在 Compose 中,状态容器通常分为两种主要类型,分别负责不同层级的逻辑

普通的状态容器类

用于管理 UI 逻辑 和 UI 元素状态。

它通常是一个普通的 Kotlin 类,生命周期与它所在的 Composition 绑定。

  • 适用场景:处理动画状态、抽屉导航(Drawer)的打开/关闭、列表的滚动位置、控制 Snackbar 的显示等。
  • 实现方式:通常会配合一个带有 remember 的工厂函数来创建和缓存该容器实例。

示例:

kotlin 复制代码
// 1. 定义状态容器类
class MyAppState(
    val scaffoldState: ScaffoldState,
    val coroutineScope: CoroutineScope
) {
    // 封装 UI 逻辑:打开侧边栏
    fun openDrawer() {
        coroutineScope.launch {
            scaffoldState.drawerState.open()
        }
    }
}

// 2. 提供一个 remember 函数供 Composable 使用
@Composable
fun rememberMyAppState(
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    coroutineScope: CoroutineScope = rememberCoroutineScope()
): MyAppState {
    return remember(scaffoldState, coroutineScope) {
        MyAppState(scaffoldState, coroutineScope)
    }
}

// 3. 在 UI 中使用状态容器
@Composable
fun MyApp() {
    // 获取状态容器实例
    val appState = rememberMyAppState()

    Scaffold(
        scaffoldState = appState.scaffoldState,
        drawerContent = { /* 侧边栏内容 */ }
    ) {
        Button(onClick = { appState.openDrawer() }) {
            Text("打开侧边栏")
        }
    }
}

ViewModel

用于管理 业务逻辑 和 屏幕级别的状态

其生命周期比普通的 Composable 更长,能够在使用配置更改(如屏幕旋转)时存活下来。

  • 适用场景:发起网络请求、读取数据库、处理用户登录等核心业务逻辑,以及保存屏幕的主数据状态。
  • 实现方式:继承 androidx.lifecycle.ViewModel,内部使用 StateFlow 或 Compose 的 State 来暴露数据。

示例:

kotlin 复制代码
class UserViewModel : ViewModel() {
    // 暴露给 Compose 的屏幕状态
    var userName by mutableStateOf("")
        private set

    // 封装业务逻辑
    fun fetchUser() {
        viewModelScope.launch {
            // 模拟网络请求
            userName = "张三"
        }
    }
}

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    // Composable 只负责渲染 ViewModel 提供的数据,以及将事件传递给 ViewModel
    Column {
        Text("当前用户: ${viewModel.userName}")
        Button(onClick = { viewModel.fetchUser() }) {
            Text("获取用户数据")
        }
    }
}
相关推荐
KIHU快狐11 小时前
快狐KIHU|110寸壁挂触控一体机G+G电容屏安卓系统汽车展厅查询展示
android·python·汽车
Fate_I_C12 小时前
Android DataBinding数据绑定表达式、双向绑定
android·kotlin·databinding
csj5013 小时前
安卓基础之《(29)—消息机制与异步任务》
android
张风捷特烈13 小时前
状态管理大乱斗#02 | Bloc 源码全面评析
android·前端·flutter
untE EADO14 小时前
MySQL错误-this is incompatible with sql_mode=only_full_group_by完美解决方案
android·sql·mysql
诸神黄昏EX14 小时前
Android Google EDLA
android
常利兵14 小时前
从0到1,开启Android音视频开发之旅
android·音视频
2501_9371454115 小时前
TV 影视大全:多品类聚合 稳定播放优化版
android·源码·源代码管理
followYouself15 小时前
Gradle、AGP、Plugin插件基本知识
android·gradle·plugin·agp
我命由我1234515 小时前
Android 开发问题:Unresolved reference: kapt
android·java·java-ee·android studio·android jetpack·android-studio·android runtime