【Android】【Compose】Compose知识点复习(二)

可观察状态
状态类型与核心特点对照表
状态类型 核心特点 典型场景
mutableStateOf 基础单值可观察状态,修改 value 触发重组 计数器、开关、输入框文本、按钮状态
mutableStateListOf 列表元素变化触发重组 购物车、待办清单、动态列表
mutableStateMapOf Map 键值对变化触发重组 用户配置、筛选条件、键值对数据
StateFlow + collectAsState 跨组件共享状态,支持异步 页面级共享状态、异步数据更新
derivedStateOf 派生计算,仅结果变化触发重组(性能优化) 滚动阈值、多状态组合计算
Flow + collectAsState 普通 Flow 转可观察状态 单次异步请求(网络/数据库)
remember ()

remember() 是 Compose 的 "状态存储器",作用是在组件重组时保留状态值,避免每次重组都重新创建状态(比如mutableStateOf)。

通俗理解

把 Compose 重组比作 "教室换座位":

没有remember():每次换座位,你都把笔记本扔了,重新拿个新本(mutableStateOf(0)重新创建,值重置为 0);

有remember():换座位时把笔记本装在书包里带走(状态被保留,值不会重置);

注意:remember() 只在 "同一重组作用域" 内有效 ------ 如果组件被销毁重建(比如页面跳转后返回),状态还是会丢失(此时需要用rememberSaveable,但核心是remember的 "记忆" 特性)。

踩坑提醒:一定要加 remember!

如果忘记加remember(),会出现 "点击加 1 后,一重组就变回 0" 的诡异问题:

java 复制代码
@Composable
fun RememberPitfallDemo() {
    // 错误:无remember,每次重组都重置为0
    val noRememberState = mutableStateOf(0)
    // 正确:有remember,重组时保留值
    val rememberState = remember { mutableStateOf(0) }

    Column {
        Text("无remember:${noRememberState.value}") // 永远是0(重组就重置)
        Text("有remember:${rememberState.value}") // 保留当前值
        Button(onClick = { 
            noRememberState.value++ 
            rememberState.value++ 
        }) { Text("点击加1") }
    }
}

日常开发中,remember和mutableStateOf几乎是 "绑定使用" 的:

java 复制代码
// 标准写法:创建可观察状态并保留
var count by remember { mutableStateOf(0) }
= vs by
写法 变量类型 读取方式 修改方式 本质 适用场景
val state = remember { mutableStateOf(0) } MutableState<Int>(容器) state.value state.value++ 直接持有状态容器本身 需要操作容器(传递/比较)
var count by remember { mutableStateOf(0) } Int(容器内的值) count count++ 通过委托访问容器内的值 简化语法,直接操作值
通俗理解

把MutableState比作 "带盖子的盒子":

= 写法:你直接拿到了 "整个盒子",要拿 / 放里面的东西,必须先打开盖子(.value);

by 写法:你委托别人帮你管盒子,不用碰盒子本身,直接拿 / 放里面的东西(编译器自动开盖子)。

by 是 Kotlin 的委托语法糖,Compose 给MutableState实现了ReadWriteProperty接口,编译器会自动帮你补全.value:

java 复制代码
// by 写法的等价代码(编译器自动生成)
val countDelegate = remember { mutableStateOf(0) }
var count: Int
    get() = countDelegate.value // 读取时自动加.value
    set(value) { countDelegate.value = value } // 修改时自动加.value
注意点

by 写法必须用var(因为要修改值),用val会编译报错;

= 写法建议用val(容器本身不用重新赋值,只改内部.value);

两种写法的重组效果完全一致,没有性能差异,仅语法不同。

无状态 + 状态提升 + 单向数据流
概念 核心定义
无状态组件 组件内部不持有状态,所有状态由外部传入,仅负责"展示 UI"和"转发事件"
状态提升 将组件的内部状态"提到"父组件管理,子组件通过参数接收状态和回调函数
单向数据流 状态只能"从父到子"传递(读),修改需通过子组件调用父组件回调(写),形成闭环
通俗理解:用 "餐厅点餐" 类比

无状态组件 = 服务员:不记顾客点了什么(不持有状态),只负责把点餐需求传给前台(父组件),把菜品端给顾客(展示 UI);

状态提升 = 点餐状态交给前台:服务员(子组件)不存菜单,所有点餐信息集中在前台(父组件),避免多个服务员记的菜单不一致;

单向数据流 = 点餐流程:顾客→服务员→前台→后厨(状态传递:父→子),后厨做好菜→前台→服务员→顾客(事件回调:子→父),全程单向,不混乱。

反例 vs 正确示例
反例(有状态组件,不推荐)

子组件自己持有状态,父组件无法控制,复用性差:

java 复制代码
// 有状态子组件:内部持有count,父组件无法获取/修改
@Composable
fun BadCounter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) { Text("计数:$count") }
}
正确示例(无状态 + 状态提升 + 单向数据流)
java 复制代码
// 1. 无状态子组件:只接收状态和回调,不持有状态
@Composable
fun GoodCounter(
    count: Int, // 从父组件接收状态(读)
    onCountAdd: () -> Unit // 从父组件接收回调(写)
) {
    Button(onClick = onCountAdd) { Text("计数:$count") }
}

// 2. 父组件:管理状态,实现单向数据流
@Composable
fun CounterParent() {
    var count by remember { mutableStateOf(0) } // 父组件持有状态
    Column {
        // 状态从父到子(读),修改通过回调(写)→ 单向数据流
        GoodCounter(count = count, onCountAdd = { count++ })
        Text("父组件同步显示:$count") // 父组件可复用状态,灵活扩展
    }
}
核心优势

无状态组件:可复用性强(同一个组件传入不同状态就能展示不同内容)、易测试(传固定状态即可验证 UI);

状态提升:状态集中管理,避免多个组件持有同一份状态导致数据不一致;

单向数据流:状态变化可追踪,所有修改都通过回调函数,调试时能快速找到 "谁改了状态"。

相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker19 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android