Jetpack Compose -> 状态机制的背后秘密

前言


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

List


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

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 的变化是不是可以及时的更新到结果,运行看下:

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

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

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 的文字")
                    }
                }
            }
        }
    }
}

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

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

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的操作,我们来运行看下效果:

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

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

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

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

我们来看一个 ReCompose 的过程:

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,我们运行看下效果:

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

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

重新赋值

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

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

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

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

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

mustableStateListOf

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

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 来监听内部的变化,我们运行看下效果;

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

mustableStateMapOf

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

总结


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

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

下一章预告


重组的性能风险和优化

欢迎三连


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

相关推荐
m0_748235954 小时前
CentOS 7使用RPM安装MySQL
android·mysql·centos
ac-er88887 小时前
Yii框架中的队列:如何实现异步操作
android·开发语言·php
流氓也是种气质 _Cookie9 小时前
uniapp 在线更新应用
android·uniapp
zhangphil11 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲12 小时前
CentOS7使用源码安装PHP8教程整理
android
编程、小哥哥13 小时前
python操作mysql
android·python
Couvrir洪荒猛兽13 小时前
Android实训十 数据存储和访问
android
五味香15 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录16 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽17 小时前
Android实训九 数据存储和访问
android