前言
上一章我们讲解了 Jetpack Compose 的无状态、状态提升、单向数据流 本章我们讲解下状态机制的背后秘密
List
前面我们讲过,通过 by mustableStateOf() 就可以被 Compose 自动订阅了;我们前面是通过 String 类型进行的自动订阅,那么换成其他类型是可以的吗?答案是可以的,只要被 mustableStateOf 包裹之后,它就会被一个 MustableState 包裹,这个 MustableState 就是一个代理对象,状态的订阅和更新会被代理到它上面,所以 我们使用其他类型也是可以的,我们可以来看一个例子:
private var number by mutableStateOf(1)@OptIn(ExperimentalMaterial3Api::class)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Android_VMTheme {Surface {Text(text = "当前数值是: $number", modifier = Modifier.clickable {number ++})}}}}
我们执行 Text 的点击事件,让 number++ 来看下 number 的变化是不是可以及时的更新到结果,运行看下:
可以看到,是可以实时的更新的,所以说,换成其他类型,也是可以的;
这个时候,可能会有人有疑问了,那么换成非基本数据类型可以吗?比如换成 List,好,我们来试一下
private var nums by mutableStateOf(mutableListOf(1, 2, 3))@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Android_VMTheme {Surface {Column {for (num in nums) {Text(text = "当前是第 $num 的文字")}}}}}
}
我们来运行看下是否是我们想要的效果:
达到了我们期望的效果,但是,接下来,我们对这个 List 进行一下修改看下界面是否还会跟着改变,怎么修改呢?我们可以继续使用点击监听的逻辑;
private var nums by mutableStateOf(mutableListOf(1, 2, 3))@OptIn(ExperimentalMaterial3Api::class)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Android_VMTheme {Surface {Button(onClick = {nums.add(nums.last() + 1)}) {Text(text = "添加内容")}Column {for (num in nums) {Text(text = "当前是第 $num 的文字")}}}}}}
我们额外增加了一个 Button,点击的时候,每点击一次,就给 List 增加一项内容,每次取最后一个值进行加1的操作,我们来运行看下效果:
可以看到,并没有达到我们期望的效果,界面内容并没有随着 List 内容的改变而改变,那么这又是为什么呢?我们来一探究竟;
我们先来想一下,这个被 by mustableStateOf 初始化的对象为什么可以被监听?因为它的get 和 set 函数被加了钩子,它的赋值和取值操作被代理了,所以它能够被监听,也就是 nums 的赋值取值被 mustableStateOf 代理了,所以它能够被监听。这个 nums 读的地方在 for 循环中被读取,那么『写』的地方是在哪里呢?是在 Button 的点击监听中更新了,这种写法看起来是没有问题的呀?那么它到底是哪里不对呢?
其实,就是在 nums 更新的地方不对!
nums 的 set 被加了钩子,是针对的 nums 的 set 方法,而不是 add 方法,所以这个改动是不生效的!也就是说 add 逻辑不会触发 setValue 的调用,所以这个改动不生效,也就不会触发自动更新的操作了;也就是说 如果我们强行增加一个 ReCompose 的过程,它的结果是会更新的;
我们来看一个 ReCompose 的过程:
private var number by mutableStateOf(1)
private var nums by mutableStateOf(mutableListOf(1, 2, 3))@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Android_VMTheme {Surface {Column {Text(text = "当前数值是: $number", modifier = Modifier.clickable {number ++})Button(onClick = {nums.add(nums.last() + 1)}) {Text(text = "添加内容")}Column {for (num in nums) {Text(text = "当前是第 $num 的文字")}}}}}}
}
我们在按钮的上面增加了一个 文字,给这个文字增加了点击监听,同时更改这个 number 的值,因为这些整体是被一个 Column 包裹,那么当 number 改变的时候,整个的区域会被 ReCompose,我们运行看下效果:
可以看到,当 number 改变的时候,List 的更新也呈现了出来;
所以,Compose 的监听更新是对 『赋值』操作的监听更新,像这种『nums.add(nums.last() + 1)』修改内部状态的是不会触发更新的,从而不会触发界面的刷新;
重新赋值
问题定位了,那么我们怎么来实现界面的刷新呢?首先大家想到的肯定是重新赋值,怎么赋值呢?
Button(onClick = {nums = nums.toMutableList().apply {add(nums.last() + 1)}
}) {Text(text = "添加内容")
}
通过 nums.toMutableList() 转换成一个新的 list 之后赋值给 nums,这样就是执行了一个『赋值』操作,我们运行看下效果:
我们通过点击,直接实现了界面的刷新操作~
但是,写到这里的时候,好多人会提出疑问了,这种写法会不会带来性能问题,以及这种写法是不是太笨重了,有没有更优雅的写法呢?答案是,不会带来性能问题,有的~
mustableStateListOf
Compose 针对 List 给我们提供另一个 API,叫作 mustableStateListOf,它内部也会创建一个 MustableStateList,并且它内部的变化也会被 Compose 观测到;
private var nums = mutableStateListOf(1, 2, 3)@OptIn(ExperimentalMaterial3Api::class)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Android_VMTheme {Surface {Column {Text(text = "当前数值是: $number", modifier = Modifier.clickable {number ++})Button(onClick = {nums.add(nums.last() + 1)}) {Text(text = "添加内容")}Column {for (num in nums) {Text(text = "当前是第 $num 的文字")}}}}}}}
我们直接使用 mutableStateListOf 来监听内部的变化,我们运行看下效果;
完美的实现了状态变化的界面刷新,并且比起前一种写法要好了很多;
mustableStateMapOf
跟 mutableStateListOf 比较相似的是 mustableStateMapOf 它是创建的一个 Map,并且监听这个 Map 的内部变化;
总结
Compose 里面用 mustableStateOf 创造出的 MustableState 是很简单的判断『是否重新赋值』 所以其无法监听普通的 List 和 Map,包括普通的 mustableListOf 和 mustableMapOf, 只能使用 mutableStateListOf 和 mustableStateMapOf 来解决;
好了,Compose 的课程今天就讲到这里吧~~
下一章预告
重组的性能风险和优化
欢迎三连
来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~