Android Jetpack Compose之UI的重组和自动刷新

1.概述

我们都知道,在传统的View中,若要改变UI,需要我们修改View的私有属性,比如要修改一个TextView的文字,我们需要通过它的setText(“xxx”)方法去修改。而Compose 则是通过重组来刷新UI。在之前的状态管理的文章中也提到过重组的概念。本章主要就是介绍Compose的重组和刷新相关的内容

2.Compose智能重组

compose的重组是很智能的,当重组发生的时候,只有状态发生改变的Composable函数才会参与重组,没有变化的Composable则会跳过本次重组。例如在计数器的demo中:

class ComposeCounterAct : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {MyComposeTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {CounterComponent()}}}}@Composablefun CounterComponent() {Column(modifier = Modifier.padding(16.dp)) {var counter by remember { mutableStateOf(0) }Text("$counter",Modifier.fillMaxWidth(),textAlign = TextAlign.Center)Row {Button(onClick = { counter-- },modifier = Modifier.weight(1f)) {Text("-")}Spacer(Modifier.width(16.dp))Button(onClick = { counter++ },modifier = Modifier.weight(1f)) {Text("+")}}}}
}

当点击Button按钮的时候,counter的状态变化会触发整个Coloum范围的重组。重组过程中显示计数器值的文字组件会被赋予新的counter值,以显示更新后的数字。但是如果此时有另外一个不依赖counter状态的文字组件,则它是不参与重组的,因为Compose编译器会在编译期间插入相关的比较代码,这些比较代码会让没有依赖变更的状态的组件,在对应状态更新时,不参与重组。

有读者可能会疑惑,在Button的onclick方法中也依赖了counter,那它会发生重组吗?答案是不会,因为重组只会在Composable函数中进行,而onClick并非是一个Composable函数,所以和重组无关。此外,Button控件也没有依赖counter状态,所以也不会参与重组

3.避免掉入重组的“坑”

在前面的文章中我们提到过,Composable在编译期间代码会发生变化,所以代码的实际运行情况可能并不如我们预期的那样。所以我们需要了解下Composable在重组执行时的一些特性,避免掉入重组的"坑”。所以我们需要理解掌握下面的内容:

3.1 Composable函数的执行顺序不固定

当我们的代码中出现多个Composable函数时,他们并不一定会按照代码中出现的顺序执行,比如在Navigation中处于Stack最上方的UI会优先被绘制,在一个Box布局中处于前景的UI会具有较高的优先级,因此Composable函数会根据优先级执行。例如:

   @Composablefun ButtonRow(){NavigationBar { StartScreen()MiddleScreen()EndScreen()}}

如上面的代码所示,在代码中ButtonRow函数一次调用了 StartScreen()、MiddleScreen()、 EndScreen()三个方法,,我们不能预设这三个方法一定是按照顺序执行的。也不要试图去在StartScreen中添加一个全局变量,然后在MiddleScreen()中获取到这个变化,这种关联被称作副作用,在Web前端开发的Vue中也有这个概念。副作用会让我们的UI状态混乱,而且不易维护,所以我们编写Compose时应该尽量避免副作用。

3.2 Composable 会并发执行

Composable在进行重组时不一定执行在主线程中,他们可能在后台线程池中并行执行,这样有利于发挥多核处理器的性能优势,但是由于多个composable在同一时间执行在不同线程,此时必须要注意考虑线程安全的问题。如下面例子所示来自于《Jetpack Compose 实战》一书:

 @Composablefun EventsFeed(localEvents:List<Event>,nationalEvent:List<Event>){var totalEvents = 0Row{Column { // 注释1localEvents.forEach { event -> Text("Item: ${event.name}") totalEvents ++}}Spacer(modifier = Modifier.height(10.dp))Column { // 注释2nationalEvent.forEach { event -> Text("Item: ${event.name}") totalEvents ++}}Text(if(totalEvents == 0) "No Events." else "Total events $totalEvents")}}

在上面的例子中想通过totalEvents记录events的数量并在Text上显示,但是注释1和注释2 处的Column代码有可能在不同的线程中并行执行,所以就导致了totalEvents的累加是非线程安全的,结果可能是不正确的。即使totalEvents的结果正确,但是由于Text可能运行在单独线程,所以也不一定能显示正确的结果,所以这里还是副作用带来的问题,需要我们避开。

3.3 Composable会反复的执行

除了重组会造成Composable的再次执行外,在动画等场景中每一帧的变化都可能引起Composable的执行。所以Composable在短时间内可能会反复的执行,而且我们无法准确的判断它的执行次数。因此我们必须考虑到:即使多次执行也不应该出现性能问题,更不能对外部产生额外有害的影响。同样。借用书中的例子:

 @Composablefun EventsFeed(netWorkService: EventsNetWorkService) {val events = netWorkService.loadAllEvents()LazyColumn {items(events) { event ->{Text(text = event.name)}}}}

如上面代码所示,在方法EventsFeed中,loadAllEvents是一个IO操作,是一个耗时操作,执行的成本高如果在Composable中同步调用,那么在重组时会造成严重的卡顿问题。所以我们编写代码的时候需要注意Composable会反复的执行,编写UI的时候需要时刻注意这一点

3.4.Composable的执行时“乐观”的

所谓的“乐观”是指Composable最终总会依据最新的状态正确地完成重组。某些场景下,UI状态可能会连续的发生变化,这可能会导致中间态的重组在执行中被打断,新的重组会插入进来。对于被打断的重组,Composable不会将执行一般的重组结果反馈到视图树上,因为它 知道最后一次状态总归是正确的,所以中间的状态丢失了也不影响。

4 总结

本节介绍了Compose的智能重组和刷新,以及重组过程中可能会掉入的“坑”,这些坑我们需要去属性它,因为Compose框架要求Composable作为一个无副作用的纯函数运行,我们只要在开发的过程中遵循这一原则,那么重组中的“坑”就不会再是坑,而是我们提高程序执行性能的有效方法。


为了帮助大家更好的熟知Jetpack Compose 这一套体系的知识点,这里记录比较全比较细致的《Jetpack 入门到精通》(内含Compose) 学习笔记!!! 对Jetpose Compose这块感兴趣的小伙伴可以参考学习下……

Jetpack 全家桶(Compose)

Jetpack 部分

  1. Jetpack之Lifecycle
  2. Jetpack之ViewModel
  3. Jetpack之DataBinding
  4. Jetpack之Navigation
  5. Jetpack之LiveData

Compose 部分
1.Jetpack Compose入门详解
2.Compose学习笔记
3.Compose 动画使用详解

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

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

相关文章

MyBatis的动态 SQL、代理机制与多级缓存

MyBatis的动态 SQL、代理机制与多级缓存 前言一、动态 SQL二、代理机制三、多级缓存最后 前言 MyBatis 是一个开源的 Java 持久层框架&#xff0c;它通过 XML 或注解的方式将 SQL 语句与 Java 对象进行映射。相比于传统的 JDBC 操作数据库&#xff0c;MyBatis 简化了很多重复的…

uni-app获取地理位置

在uni-app中&#xff0c;可以通过uni.getLocation()方法获取地理位置。具体步骤如下&#xff1a; 在uni-app项目中的manifest.json文件中&#xff0c;添加需要获取地理位置的权限&#xff1a; {"mp-weixin": {"appid": "...","permission…

【CSS】画个三角形或圆形或环

首先通过调整边框&#xff0c;我们可以发现一些端倪 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style>.box{width: 150px;height:150px;border: 50px solid black;}</style&g…

[JAVEee]SpringBoot项目的创建

SpringBoot可以更好的开发Spring项目,本文章将使用idea社区版来演示创建项目的过程与注意事项. SpringBoot的优点 SpringBoot中内置快速添加依赖的功能,能够便捷的集成各种框架,帮助开发.内置运行容器,无需配置Tomcat容器等其他web容器,可直接进行项目的部署与运行.更好的使用…

2023年中国研究生数学建模竞赛D题解题思路

为了更好的帮助大家第一天选题&#xff0c;这里首先为大家带来D题解题思路&#xff0c;分析对应赛题之后做题阶段可能会遇到的各种难点。 稍后会带来D题的详细解析思路&#xff0c;以及相关的其他版本解题思路 成品论文等资料。 赛题难度评估&#xff1a;A、B>C>E、F&g…

Git创建干净分支,本地操作不依赖任何分支

clone远程项目: git clone gittest.git查看分支: git branch -a创建新分支: git checkout --orphan test, 返回Switched to a new branch test删除当前项目文件夹下所有文件: git rm -rf .提交变更: git commit -m "new branch for test"查看分支: git branch -a, 发…

MDK工程转换Vscode+EIDE方法

MDK工程转换VscodeEIDE方法 1、VscodeEIDE环境搭建方法 请按下方视频完成环境搭建&#xff0c;并编译成功。下载&#xff0c;单步调试如无视频中芯片可暂不执行。 https://www.bilibili.com/video/BV1Zu4y1f72H/?spm_id_from333.337.search-card.all.click&vd_source73…

flex布局与float布局

float布局 俩栏 三栏 flex布局

如何快速实现一个可视化看板?

一、用python实现一个可视化数据看板&#xff0c;最多支持多大体量的数据处理&#xff1f; Python可以通过多种可视化库来实现数据看板&#xff0c;例如Matplotlib、Seaborn、Plotly等。这些库可以处理各种规模的数据&#xff0c;从小型数据集到大型数据集都可以应用。 对于小型…

深拷贝和浅拷贝的区别

本文内容 主要阐述下深拷贝和浅拷贝的区别 通俗理解 深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体&#xff0c;而不是引用。 假设B复制了A&#xff0c;修改A的时候&#xff0c;看B是否发生变化&#xff1a; 如果B跟着也变了&#xff0c;说明是浅拷贝&…

第一百五十一回 自定义组件综合实例:游戏摇杆二

文章目录 内容回顾实现方法位置细节示例代码我们在上一章回中介绍了如何实现 游戏摇杆相关的内容,本章回中将继续介绍这方面的知识.闲话休提,让我们一起Talk Flutter吧。 内容回顾 我们在上一章回中介绍了游戏摇杆的概念以及实现方法,并且通过示例代码演示了实现游戏摇杆的…

MLAgents (0) Unity 安装及运行

1、下载ML-Agents 下载地址 GitHub - Unity-Technologies/ml-agents: The Unity Machine Learning Agents Toolkit (ML-Agents) is an open-source project that enables games and simulations to serve as environments for training intelligent agents using deep reinfo…

7.2、如何理解Flink中的水位线(Watermark)

目录 0、版本说明 1、什么是水位线&#xff1f; 2、水位线使用场景&#xff1f; 3、设计水位线主要为了解决什么问题&#xff1f; 4、怎样在flink中生成水位线&#xff1f; 4.1、自定义标记 Watermark 生成器 4.2、自定义周期性 Watermark 生成器 4.3、内置Watermark生…

软考网络工程师华为配置考点总结

华为交换机配置基础 1.vlan的配置 华为设备中划分VLAN的方式有&#xff1a; 静态的划分&#xff1a;基于接口动态划分&#xff1a;基于MAC地址、基于IP子网、基于协议、基于策略&#xff08;MAC地址、Ip地址&#xff09;。 其中基于接口划分VLAN&#xff0c;是最简单&#x…

AI视频剪辑:批量智剪技巧大揭秘

对于许多内容创作者来说&#xff0c;视频剪辑是一项必不可少的技能。然而&#xff0c;传统的视频剪辑方法需要耗费大量的时间和精力。如今&#xff0c;有一种全新的剪辑方式正在改变这一现状&#xff0c;那就是批量AI智剪。这种智能化的剪辑方式能够让你在短时间内轻松剪辑大量…

Fourier变换的积分性质及其证明过程

Fourier变换的积分性质及其证明过程 一、积分性质 如果当 t → ∞ t \to \infty t→∞时&#xff0c; g ( t ) ∫ − ∞ t f ( t ) d t → 0 g(t) \int_{ - \infty }^t {f(t){\rm{d}}t \to 0} g(t)∫−∞t​f(t)dt→0&#xff0c;则&#xff1a; F [ ∫ − ∞ t f ( t ) …

2591. 将钱分给最多的儿童(Java)

给你一个整数 money &#xff0c;表示你总共有的钱数&#xff08;单位为美元&#xff09;和另一个整数 children &#xff0c;表示你要将钱分配给多少个儿童。 你需要按照如下规则分配&#xff1a; 所有的钱都必须被分配。 每个儿童至少获得 1 美元。 没有人获得 4 美元。 请你…

代码随想录算法训练营第23期day3| 203.移除链表元素 ,707.设计链表,206.反转链表

目录 一、链表 基础操作 二、&#xff08;leetcode 203&#xff09;移除链表元素 1.使用原来的链表 2.设置虚拟头结点 三、&#xff08;leetcode 707&#xff09;设计链表 四、&#xff08;leetcode 206&#xff09;反转链表 1.双指针法 2.递归法 一、链表 单链表定义…

大型集团借力泛微搭建语言汇率时区统一、业务协同的国际化OA系统

国际化、全球化集团&#xff0c;业务遍布全世界&#xff0c;下属公司众多&#xff0c;集团对管理方式和企业文化塑造有着很高的要求。不少大型集团以数字化方式助力全球统一办公&#xff0c;深化企业统一管理。 面对大型集团全球化的管理诉求&#xff0c;数字化办公系统作为集…

观测云产品更新 | 优化日志数据转发、索引绑定、基础设施自定义等

观测云更新 日志 数据转发&#xff1a;新增外部存储转发规则数据查询&#xff1b;支持启用/禁用转发规则&#xff1b;绑定索引&#xff1a;日志易新增标签绑定&#xff0c;从而实现更细颗粒度的数据范围查询授权能力。 基础设施 > 自定义 【默认属性】这一概念更改为【必…