Jetpack Compose -> mutableStat

前言


上一章我们讲解了 Jetpack Compose 的无状态、状态提升、单向数据流 本章我们讲解下状态机制的背后秘密

List


前面我们讲过,通过 by mutableStateOf() 就可以被 Compose 自动订阅了;我们前面是通过 String 类型进行的自动订阅,那么换成其他类型是可以的吗?答案是可以的,只要被 mutableStateOf 包裹之后,它就会被一个 MutableState 包裹,这个 MutableState 就是一个代理对象,状态的订阅和更新会被代理到它上面,所以 我们使用其他类型也是可以的,我们可以来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kotlin复制代码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 的变化是不是可以及时的更新到结果,运行看下:

SVID_20240408_122244_1.gif

可以看到,是可以实时的更新的,所以说,换成其他类型,也是可以的;

这个时候,可能会有人有疑问了,那么换成非基本数据类型可以吗?比如换成 List,好,我们来试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kotlin复制代码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 的文字")
}
}
}
}
}
}

我们来运行看下是否是我们想要的效果:

image.png

达到了我们期望的效果,但是,接下来,我们对这个 List 进行一下修改看下界面是否还会跟着改变,怎么修改呢?我们可以继续使用点击监听的逻辑;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kotlin复制代码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的操作,我们来运行看下效果:

SVID_20240408_123643_1.gif

可以看到,并没有达到我们期望的效果,界面内容并没有随着 List 内容的改变而改变,那么这又是为什么呢?我们来一探究竟;

我们先来想一下,这个被 by mutableStateOf 初始化的对象为什么可以被监听?因为它的get 和 set 函数被加了钩子,它的赋值和取值操作被代理了,所以它能够被监听,也就是 nums 的赋值取值被 mutableStateOf 代理了,所以它能够被监听。这个 nums 读的地方在 for 循环中被读取,那么『写』的地方是在哪里呢?是在 Button 的点击监听中更新了,这种写法看起来是没有问题的呀?那么它到底是哪里不对呢?

其实,就是在 nums 更新的地方不对!

nums 的 set 被加了钩子,是针对的 nums 的 set 方法,而不是 add 方法,所以这个改动是不生效的!也就是说 add 逻辑不会触发 setValue 的调用,所以这个改动不生效,也就不会触发自动更新的操作了;也就是说 如果我们强行增加一个 ReCompose 的过程,它的结果是会更新的;

我们来看一个 ReCompose 的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
kotlin复制代码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,我们运行看下效果:

SVID_20240408_125301_1.gif

可以看到,当 number 改变的时候,List 的更新也呈现了出来;

所以,Compose 的监听更新是对 『赋值』操作的监听更新,像这种『nums.add(nums.last() + 1)』修改内部状态的是不会触发更新的,从而不会触发界面的刷新;

重新赋值

问题定位了,那么我们怎么来实现界面的刷新呢?首先大家想到的肯定是重新赋值,怎么赋值呢?

1
2
3
4
5
6
7
ini复制代码Button(onClick = {
nums = nums.toMutableList().apply {
add(nums.last() + 1)
}
}) {
Text(text = "添加内容")
}

通过 nums.toMutableList() 转换成一个新的 list 之后赋值给 nums,这样就是执行了一个『赋值』操作,我们运行看下效果:

SVID_20240408_130329_1.gif

我们通过点击,直接实现了界面的刷新操作~

但是,写到这里的时候,好多人会提出疑问了,这种写法会不会带来性能问题,以及这种写法是不是太笨重了,有没有更优雅的写法呢?答案是,不会带来性能问题,有的~

mustableStateListOf

Compose 针对 List 给我们提供另一个 API,叫作 mutableStateListOf,它内部也会创建一个 MutableStateList,并且它内部的变化也会被 Compose 观测到;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
kotlin复制代码    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 来监听内部的变化,我们运行看下效果;

SVID_20240408_131312_1.gif

完美的实现了状态变化的界面刷新,并且比起前一种写法要好了很多;

mustableStateMapOf

跟 mutableStateListOf 比较相似的是 mutableStateMapOf 它是创建的一个 Map,并且监听这个 Map 的内部变化;

总结


Compose 里面用 mutableStateOf 创造出的 MutableState 是很简单的判断『是否重新赋值』 所以其无法监听普通的 List 和 Map,包括普通的 mutableListOf 和 mutableMapOf, 只能使用 mutableStateListOf 和 mutableStateMapOf 来解决;

好了,Compose 的课程今天就讲到这里吧~~

下一章预告


重组的性能风险和优化

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%