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…

电容隔离型±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. …

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或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术…

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

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

Redis 的主从复制、哨兵和cluster集群

目录 一. Redis 主从复制 1. 介绍 2. 作用 3. 流程 4. 搭建 Redis 主从复制 安装redis 修改 master 的Redis配置文件 修改 slave 的Redis配置文件 验证主从效果 二. Redis 哨兵模式 1. 介绍 2. 原理 3. 哨兵模式的作用 4. 工作流程 4.1 故障转移机制 4.2 主节…

记录一次内网渗透过程

0x01 前言: 一切以学习为主,记录一次小小的攻击过程 本次是通过外网漏洞撕开的口子,主要通过一下方式 拿到了目标资产 nday扫一扫 弱口令爆一爆 上传接口找一找 后台上传找一找 数据库弱口令 关注新day,有了立马在资产里面…

K8s学习三(Pod与探针)

深入学习Pod Pod配置文件 写一个自己的配置文件,nginx-po.yaml apiVersion: v1 #api文档版本 kind: Pod #资源类型对象,也可以配置为像Development,StatefulSet这一类的对象 metadata: # Pod相关的元数据,用于描述Pod的数据name: nginx-po…

深度比较Vue 3.0中的computed和watch属性用法与最佳实践

摘要:在Vue 3.0中,computed和watch属性是用于处理数据逻辑的重要工具。本文将详细对比这两个属性的工作原理、适用场景以及使用时的注意事项,旨在帮助开发者更有效地选择和使用它们。 一、computed属性 computed属性是Vue 3.0中用于计算数据…

【随笔】Git 高级篇 -- 相对引用2 HEAD~n(十三)

💌 所属专栏:【Git】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! 💖 欢迎大…

D. Constructing the Array Codeforces Round 642 (Div. 3)

题目链接:Problem - 1353D - Codeforceshttps://codeforces.com/problemset/problem/1353/D 题目大意: 往空的数组里从1~n填数字,每次填最长的空区间的中间位置(位置向下取整)。 思路: 用二分判断放每一个数…

【信贷后台管理之(五)】

文章目录 目录结构一、面包屑组件封装二、退出登录接口联调三、申请列表的菜单路由3.1 路由创建,表格编写3.2 列表接口调用3.3 出生日期转变3.4 申请状态3.5 申请列表的操作3.5.1 编辑删除提交操作3.5.2 禁用状态3.5.3 操作接口3.5.4 搜索查询3.5.5 申请列表分页功能…

探索Python爬虫:解析网页数据的神奇之旅

在当今数字化时代,信息的获取变得比以往任何时候都更加便捷。然而,即使在互联网上,获取数据也需要通过正确的工具和技术。Python爬虫就是这样一种强大的工具,它可以让我们轻松地从互联网上收集数据,并将其转化为有用的…