前言
在Jetpack Compose我们会看到这两种定义变量的方式,很相似,但是使用起来却不一样。
1 | val name1 = remember { mutableStateOf("") } |
我们可以拆分成三个部分
- by/=
- remember/mutableStateOf
- mutableStateOf/mutableStateListOf
下面分别说一下这三者的作用。
区别
类型
- name1 是
MutableState<String>
他是可以传入子组件中,子组件中修改后,父组件中也能获取到更新后的值。 - name2 是
String
类型,传入子组件的是值
而不是MutableState
对象,子组件不能修改它的值,因为传入的值的类型是val
。
by
属性委托是 Kotlin 提供的一种特性,它允许把属性的 get()
和 set()
方法的实现委托给另一个对象。借助这种方式,能够避免编写大量样板代码,增强代码的复用性与可维护性。
基本语法格式为:
1 | var <属性名>: <类型> by <委托对象> |
by
关键字把 name2
属性的 get()
和 set()
方法的实现委托给了 remember { mutableStateOf("") }
返回的 MutableState
对象。
具体来说:
- 读取属性值:当读取
name2
的值时,实际上调用的是MutableState
对象的getValue
方法,该方法会返回MutableState
对象的value
属性。 - 设置属性值:当给
name2
赋值时,实际上调用的是MutableState
对象的setValue
方法,该方法会更新MutableState
对象的value
属性。
remember/rememberSaveable
在Compose中,remember
和rememberSaveable
都是用于保存可组合函数的状态的方法,但它们在如何保存状态以及在什么情况下会重新计算状态上有所不同。
简言之
remember
在重绘的时候保留状态,建议组件内的属性都添加remember
。rememberSaveable
不仅重绘能保留状态,销毁重建也能保留状态(例如旋转屏幕)。
remember
: 这个函数在组合函数的生命周期内始终保持相同的状态。
这意味着,每次组合函数重新调用时,它都会使用先前保存的状态值,而不会重新计算它。这样能避免不必要的计算开销。
这对于静态数据或者不会因用户交互而改变的数据很有用。如果状态的改变不需要在组件生命周期之外持久化,remember
是一个更轻量级的选择。
1 | var password by remember { mutableStateOf("") } |
rememberSaveable
: 这个函数也会保存状态,但它还会将状态持久化,以便在应用程序进入后台或被销毁后,能够恢复该状态。
这对于需要跨配置更改(例如旋转屏幕)或者应用程序生命周期的状态非常有用。
它会将状态保存在Bundle
中,以确保状态的持久化。
1 | var password by rememberSaveable { mutableStateOf("") } |
因此,rememberSaveable
提供了对状态的持久化支持,而remember
则仅在组件生命周期内保存状态。
选择使用哪种取决于您需要的状态是否需要在应用程序重新启动后保持不变。
mutableStateOf/mutableStateListOf
mutableStateOf
mutableStateOf
是 Jetpack Compose 中的一个函数,用于创建可变的状态。
它的作用是创建一个可以被修改的状态,并且当状态发生改变时,Compose 会重新计算并更新相关的 UI。
具体来说,mutableStateOf
函数接受一个初始值作为参数,并返回一个包含该初始值的 MutableState
对象。
MutableState
对象具有 value
属性,可以读取和修改该状态的值。
当 MutableState
对象的值发生改变时,Compose 会根据新的状态重新计算 UI,以确保 UI 反映最新的状态。
注意
只有组件内使用了
countState.value
才可以,如果只是使用了countState
也不行。
例如,假设我们有一个 mutableStateOf
对象来表示一个计数器的值:
1 | val countState = remember { mutableStateOf(0) } |
然后我们可以通过修改 countState.value
的值来更新计数器的状态:
1 | countState.value += 1 |
每当 countState.value
的值发生改变时,与该状态相关联的 UI 将会重新计算并更新,从而反映最新的计数器值。
总的来说:
mutableStateOf
的作用是在 Jetpack Compose 中创建可变的状态,以便动态更新 UI,并确保 UI 反映最新的状态值。
注意
如果是对象,对象的值变并不会触发渲染,必须改变引用才可以。
比如
1 | var currentPath by remember { mutableStateOf(Path()) } |
mutableStateListOf
1 | val drawPaths = remember { mutableStateListOf<DrawPath>() } |
返回的类型是SnapshotStateList<T>
。
mutableStateListOf
在以下情况下会触发重新渲染:
- 列表元素的引用发生变化。
- 列表的结构发生变化,如添加、删除或移动元素。
监听变化
在 Jetpack Compose 里,有多种方式可以监听 MutableState
的变化,下面为你详细介绍不同的实现方法:
使用 LaunchedEffect
LaunchedEffect
是一个副作用函数,它可以在组合发生时启动一个协程。你可以将 MutableState
作为 LaunchedEffect
的键,当 MutableState
的值改变时,LaunchedEffect
会自动取消并重新启动。
示例代码
1 | import androidx.compose.runtime.* |
代码解释
LaunchedEffect(count)
:把count
当作键传递给LaunchedEffect
,只要count
的值改变,LaunchedEffect
内部的代码就会重新执行。println("count 的值已更新为: $count")
:当count
变化时,打印出更新后的值。
使用 derivedStateOf
derivedStateOf
可用于创建一个派生状态,当依赖的 MutableState
发生变化时,派生状态也会更新。你可以监听派生状态的变化。
示例代码
1 | import androidx.compose.runtime.* |
代码解释
derivedStateOf { inputText.uppercase() }
:创建一个派生状态upperCaseText
,它依赖于inputText
,当inputText
改变时,upperCaseText
会自动更新为inputText
的大写形式。LaunchedEffect(upperCaseText.value)
:监听upperCaseText
的变化,当它的值改变时,执行内部代码。
使用 produceState
produceState
可以用来管理异步状态,当依赖的 MutableState
发生变化时,它会重新计算状态。
示例代码
1 | import androidx.compose.runtime.* |
代码解释
produceState(initialValue = 0) { value = number * number }
:创建一个状态squaredNumber
,当number
改变时,重新计算number
的平方并更新squaredNumber
的值。LaunchedEffect(squaredNumber.value)
:监听squaredNumber
的变化,当它的值改变时,执行内部代码。
转换为 Flow 监听(灵活适配生命周期)
如果需要更灵活的监听(如配合生命周期、在 ViewModel 中处理等),可以使用 snapshotFlow
将 MutableState
转换为 Flow
,再通过 collect
监听变化。
示例:转换为 Flow 并结合生命周期监听
1 | import androidx.compose.runtime.* |
优势:
snapshotFlow
会在MutableState
变化时发射新值,适配 Compose 的状态快照系统。- 结合
repeatOnLifecycle
可以跟随生命周期自动暂停 / 恢复收集,避免内存泄漏。
非 Composable 环境中监听(ViewModel)
如果需要在非 Composable 代码(如 ViewModel)中监听 MutableState
变化,
同样可以通过 snapshotFlow
转换为 Flow,再在 ViewModel 中收集。
示例:在 ViewModel 中监听
1 | import androidx.compose.runtime.mutableStateOf |
使用场景:
当需要在业务层(ViewModel)响应 UI 层的状态变化时(如输入校验、实时搜索等),这种方式非常合适。