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

前言


上一章我们讲解了 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 的变化是不是可以及时的更新到结果,运行看下:

SVID_20240408_122244_1.gif

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

这个时候,可能会有人有疑问了,那么换成非基本数据类型可以吗?比如换成 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 的文字")}}}}}
}

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

image.png

达到了我们期望的效果,但是,接下来,我们对这个 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的操作,我们来运行看下效果:

SVID_20240408_123643_1.gif

可以看到,并没有达到我们期望的效果,界面内容并没有随着 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,我们运行看下效果:

SVID_20240408_125301_1.gif

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

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

重新赋值

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

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

SVID_20240408_131312_1.gif

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

mustableStateMapOf

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

总结


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

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

下一章预告


重组的性能风险和优化

欢迎三连


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/799973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深度学习】YOLO-World: Real-Time Open-Vocabulary Object Detection,目标检测

介绍一个酷炫的目标检测方式: 论文:https://arxiv.org/abs/2401.17270 代码:https://github.com/AILab-CVC/YOLO-World 文章目录 摘要Introduction第2章 相关工作2.1 传统目标检测2.2 开放词汇目标检测 第3章 方法3.1 预训练公式&#xff1a…

Linux命令-dos2unix命令(将DOS格式文本文件转换成Unix格式)

说明 dos2unix命令 用来将DOS格式的文本文件转换成UNIX格式的(DOS/MAC to UNIX text file format converter)。DOS下的文本文件是以 \r\n 作为断行标志的,表示成十六进制就是0D0A。而Unix下的文本文件是以\n作为断行标志的,表示成…

vitepress系列-01-搭建笔记骨架

文章目录 搭建笔记骨架 搭建笔记骨架 环境依赖:Node.js 18 及以上版本。 项目创建-以macOS为例 # 1.创建空项目 mkdir vitepress-learn-notes# 2.进入 vitepress-learn-notes cd vitepress-learn-notes# 3. 初始化项目 pnpm init# 4.安装vitepress 根据自己电脑的安…

MongoDB数据更新中的乘法$mul

学习mongodb,体会mongodb的每一个使用细节,欢迎阅读威赞的文章。这是威赞发布的第57篇mongodb技术文章,欢迎浏览本专栏威赞发布的其他文章。 空闲下来,仔细阅读Mongodb的官方文档,发现很多新的功能。mongodb为了给开发…

【C++】STL--list

目录 list的介绍 list的使用 list的构造 list iterator的使用 list capacity list modifiers list的迭代器失效 list模拟实现 list的介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2. list的底层是双向…

电容隔离型±10V输入隔离放大器特点:ISOC 124P

产品特点: 50KHz(-3dB)高带宽与ISO 124P隔离器Pin-Pin兼容 低成本小体积,标准DIP16Pin阻燃材料封装 精度等级:0.01级,全量程内非线性度0.01% 信号输入与输出之间:3000VDC隔离耐压 电源范围:4.5V~18V 双极运算:Vo10V 方便易用,固定单位增益配置…

ubuntu安装nginx以及开启文件服务器

1. 下载源码 下载页面:https://nginx.org/en/download.html 下载地址:https://nginx.org/download/nginx-1.24.0.tar.gz curl -O https://nginx.org/download/nginx-1.24.0.tar.gz2. 依赖配置 sudo apt install gcc make libpcre3-dev zlib1g-dev ope…

【分治算法】Strassen矩阵乘法Python实现

文章目录 [toc]问题描述基础算法时间复杂性 Strassen算法时间复杂性 问题时间复杂性Python实现 个人主页:丷从心. 系列专栏:Python基础 学习指南:Python学习指南 问题描述 设 A A A和 B B B是两个 n n n \times n nn矩阵, A A…

CICD流水线 发布应用到docker镜像仓库

准备工作 1.先注册免费的镜像仓库 复制链接: https://cr.console.aliyun.com/cn-beijing/instances 实施 1. 新建流水线,选择模板 2.添加流水线源,及是你的代码仓库, 选择对应分支. 3.代码检查以及单元测试,这个步骤可以不用动它. 4. …

gcc/g++:预编译阶段寻找头文件

预编译阶段寻找头文件大致可分为两种情况:1)系统头文件,2)用户头文件。 示例: 1)用户头文件 /*brief design and implements of demo-for-precompile-to-include.author wenxuanpeiemail 15873152445163.…

AI的力量感受(附网址)

输入 科技感的 二维码,生成如下,还是可以的 输入金属感 的芯片,效果就很好了 金属感 打印机,细节丰富,丁达尔效应 就有点跑题了 金属感 扫码仪 还有点像 3D 封装长这样,跑题比较严重 总之,AI还…

如何使用生成式人工智能撰写关于新产品发布的文章?

利用生成式人工智能撰写新产品发布文章确实是一种既有创意又高效的内容生成方式。以下是如何做到这一点的指南,附带一些背景信息: • 背景:在撰写文章之前,收集有关您的新产品的信息。这包括产品的名称、类别、特点、优势、目标受…

朗汀留学美国生物医学工程专业留学部分录取案例合集

满怀期待的憧憬与金榜题名的喜悦交织着未来的掌声,捧在手心里的不仅仅是一份一份努力浇灌的录取通知,更是一起拼搏走过的岁月沉淀。 我们感恩每一位朗汀留学的学生和家长,是你们的支持与信任,让我们有机会共享此刻的荣耀&#xff…

数据挖掘及其近年来研究热点介绍

🎀个人主页: https://zhangxiaoshu.blog.csdn.net 📢欢迎大家:关注🔍点赞👍评论📝收藏⭐️,如有错误敬请指正! 💕未来很长,值得我们全力奔赴更美好的生活&…

jdk目录结构

jdk目录详解 JDK(Java Development Kit,Java开发包,Java开发工具)是一个写Java的applet和应用程序的程序开发环境。它由一个处于操作系统层之上的运行环境还有开发者 编译,调试和运行用Java语言写的applet和应用程序所需的工具组成。 JDK(J…

【数据结构】考研真题攻克与重点知识点剖析 - 第 6 篇:图

前言 本文基础知识部分来自于b站:分享笔记的好人儿的思维导图与王道考研课程,感谢大佬的开源精神,习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术…

通过 ffmpeg命令行 调节视频播放速度

1. 仅调整视频速率 视频调速原理:修改视频的pts,dts # 可能会丢帧 ffmpeg -i input.mkv -an -filter:v "setpts0.5*PTS" output.mkv # 可用-r参数指定输出视频FPS以防止丢帧 ffmpeg -i input.mkv -an -r 60 -filter:v "setpts2.0*PTS&q…

redis的HyperLogLogs详细介绍

Redis的HyperLogLogs是一种概率数据结构,用于估计集合中的元素个数,即基数。即使存储了超大数据(超过2^64的基数)的集合,HyperLogLogs也只需要12KB的内存,所有这使得其非常适合做大规模数据的去重计数。 H…

MySQL | 如何使用mysqldumpslow命令进行SQL慢查询分析?

关注WX:CodingTechWork 介绍 启用慢查询日志 编辑 MySQL 配置文件:打开 MySQL 的配置文件(通常是 my.cnf 或 my.ini)。 启用慢查询日志:找到以下配置项并进行设置: slow_query_log 1 # 启用…

爱普生语音芯片的特点与应用市场

随着物联网与智能家居的普及,越来越多的电子产品有了语音播报的需求。但是很多客户没有类似的开发经验或者他们的产品内部只能承载一个蜂鸣器。这样的情况下要如何实现快速的产品升级呢?下面让我们来看一下差普生语音芯片是如果帮助客户的。目前爱普生语音芯片分为…