Jetpack Compose | State状态管理及界面刷新

我们知道Jetpack Compose(以下简称Compose)中的 UI 可组合项是通过@Composable 声明的函数来描述的,如:

@Composable
fun Greeting() {Text(text = "init",color = Color.Red,modifier = Modifier.fillMaxWidth())
}

上面的代码描述的是一个静态的 Text,那么如何让 Compose 中的UI更新呢?

状态和重组

Compose 更新UI的唯一方法是通过新参数调用同一可组合项。可组合项中的状态更新时,就会发生重组。

State状态

mutableStateOf() 会创建可观察的 MutableState<T>,如下:

@Stable
interface MutableState<T> : State<T> {override var value: T
}

当value有任何变化时,Compose 会自动为读取 value 的所有可组合函数安排重组。但是靠State只能完成重组,并不能完成UI更新,说的有点绕,直接来看示例:

@Composable
fun Greeting() {val state = mutableStateOf("init")log("state:${state.value}")//LogcatColumn {Text(text = state.value,color = Color.Red,modifier = Modifier.fillMaxWidth())Button(onClick = { state.value = "Jetpack Compose" }) {Text(text = "点击更改文本")}}
}

多次点击按钮,执行结果如下:

14:25:34.493  E  state:init
14:25:35.919  E  state:init
14:25:37.365  E  state:init
......

可以看到点击Button按钮后确实执行重组了,但是Text中的文本并没有相应更新!这是因为每次进行重组时,可组合项Greeting() 中的 state 又被重新初始化了,导致UI并没有更新。能不能在下次进行重组时保存State<T>中的value值呢,答案是肯定的!可以结合 remember 来使用。

remember

Compose 会在初始组合期间将由 remember 计算的值存储在组合内存中,并在重组期间返回存储的值。remember 既可用于存储可变对象,又可用于存储不可变对象。我们将上面的代码修改如下:

@Composable
fun Greeting() {//前面加了remember,其他都不变val state = remember { mutableStateOf("init") }log("state:${state.value}")......
}

点击 Button 按钮后:

执行结果:

15:06:04.544  E  state:init
//点击Button按钮后:
15:06:07.313  E  state:Jetpack Compose

可以看到UI 成功的更新了。

remember(key1 = resId) { } 控制对象缓存的生命周期
@Composable
inline fun <T> remember(key1: Any?,calculation: @DisallowComposableCalls () -> T
): T {return currentComposer.cache(currentComposer.changed(key1), calculation)
}

除了缓存 State 状态之外,还可以使用 remember 将初始化或计算成本高昂的对象或操作结果存储在组合中。

如上,remember 还可以接受key参数,当key发生变化,缓存值会失效并再次对 lambda 块进行计算。这种机制可控制组合中对象的生命周期。这样带来的好处是不会在每次重组时都进行对象重建高成本操作,如:

val bitmap = remember(key1 = resId) {ShaderBrush(BitmapShader(ImageBitmap.imageResource(res, resId).asAndroidBitmap(),Shader.TileMode.REPEAT, Shader.TileMode.REPEAT))}

上述代码即使发生在频繁重组的可组合项中,只要 key1 = resId 不变,那么ShaderBrush 就不会重新创建,从而提高了性能。

rememberSaveable 与自定义Saver
  • remember 在重组后保持状态,但不会在配置更改后保持状态;
  • 如果想在配置更改后保持状态,可以使用 rememberSaveable 代替;
  • rememberSaveable 会自动保存可保存在 Bundle 中的任何值;如果不支持Bundle存储,可以将对象声明为 @Parcelize 可序列化,如果不能序列化,还可以将其传入自定义 Saver 对象。

示例:

//1、使用@Parcelize注解
//记得引入 apply plugin: 'kotlin-parcelize'插件
@Parcelize
data class CityParcel(val name: String, val country: String) : Parcelabledata class City(val name: String, val country: String)
//2、MapSaver自定义存储规则,将对象转换为系统可保存到 Bundle 的一组值。
val CityMapSaver = run {val nameKey = "Beijing"val countryKey = "China"mapSaver(save = { mapOf(nameKey to it.name, countryKey to it.country) },restore = { City(it[nameKey] as String, it[countryKey] as String) })
}
//3、ListSaver自定义存储规则
val CityListSaver = listSaver<City, Any>(save = { listOf(it.name, it.country) },restore = { City(it[0] as String, it[1] as String) }
)

可组合项中使用它们:

@Composable
fun Greeting() {
// 1、如果涉及到配置更改后的状态恢复,直接使用rememberSaveable,会将值存储到Bundle中
var parcelCity by rememberSaveable {mutableStateOf(CityParcel("Beijing", "China"))
}// 2、如果存储的值不支持Bundle,可以将Model声明为@Parcelable 或者使用MapSaver、ListSaver自定义存储规则
var mapSaverCity by rememberSaveable(stateSaver = CityMapSaver) {mutableStateOf(City("Beijing", "China"))
}var listSaverCity by rememberSaveable(stateSaver = CityListSaver) {mutableStateOf(City("Beijing", "China"))
}log("parcelCity: $parcelCity")
log("mapSaverCity: $mapSaverCity")
log("listSaverCity: $listSaverCity")
}

执行结果:

17:35:36.810  E  parcelCity: CityParcel(name=Beijing, country=China)
17:35:36.810  E  mapSaverCity: City(name=Beijing, country=China)
17:35:36.810  E  listSaverCity: City(name=Beijing, country=China)
State与 remember结合使用

一般Compose中 MutableState 都是需要跟 remember 组合使用(可乐配鸡翅,天生是一对~),在可组合项中声明 MutableState 对象的方法有三种:

val mutableState = remember { mutableStateOf("init0") } //1、返回MutableState<T>类型
var value1 by remember { mutableStateOf("init1") } //2、返回T类型
val (value2, setValue) = remember { mutableStateOf("init") } //3、返回两个值分别为:T,Function1<T, kotlin.Unit>

第二种的by委托机制是最常用的,不过需要导入:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

UI 接收重组数据的几种方式

现代 Android 架构不管是 MVVM 还是 MVI ,都会用到ViewModel,在ViewModel中通过LiveData、Flow去操作数据,并在UI 层监听数据变化,当数据变化时,UI 层根据监听到的新数据做UI刷新,也就是数据驱动。

Compose中的 UI 界面刷新思路是一样的,只不过需要将得到的数据进行一下转换而已:

  • 对于 LiveData,需要将 LiveData<T> 转换为 State<T>
  • 对于 Flow,需要将 Flow<T> 转换为 State<T>

记住必须将新数据转换为 State<T>格式,这样 Compose 才可以在状态发生变化后自动重组

Flow.collectAsState() & Flow.collectAsStateWithLifecycle()如何选择
//ViewModel层
class ComposeVModel : ViewModel(){//StateFlow UI层通过该引用观察数据变化private val _wanFlow = MutableStateFlow<List<WanModel>>(ArrayList())val mWanFlow: StateFlow<List<WanModel>> = _wanFlow//请求数据fun getWanInfoByFlow(){......}
}//UI层
import androidx.lifecycle.viewmodel.compose.viewModel@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
fun Greeting(vm: ComposeVModel = viewModel()) { //2、将 Flow<T> 转换成 State<T>val state by vm.mWanFlow.collectAsStateWithLifecycle()Column {Text(text = "$state",color = Color.Red,modifier = Modifier.fillMaxWidth())//1、点击通过ViewModel请求数据Button(onClick = { vm.getWanInfoByFlow() }) {Text(text = "点击更改文本")}}
}

上述代码1处通过Button点击进行网络请求,2处负责将 Flow<T> 转换成 State<T>,当数据有更新时,可组合项就可以进行重组,这样整个流程就串起来了。在Android 项目中,collectAsState()collectAsStateWithLifecycle() 该选择哪个使用呢?

1collectAsStateWithLifecycle() 会以生命周期感知型方式从 Flow 收集值。它通过 Compose State 表示最新发出的值,在 Android 开发中请使用这个方法来收集数据流。使用collectAsStateWithLifecycle()必须引入库:

implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0-alpha01"

2.6.0-alpha01是最低版本,因为我是在AGP7.0以下的项目中使用Compose,如需使用更高版本,自行修改吧~

2collectAsState()collectAsStateWithLifecycle() 类似,但不是生命周期感知的,通常用于跨平台的场景下(Compose也可以跨平台)。collectAsState 可在 compose-runtime 中使用,因此不需要其他依赖项。

LiveData.obseverAsState()

observeAsState() 会开始观察此 LiveData<T>,并在LiveData<T>有数据更新时,自动将其转换为State<T> ,进而触发可组合项的重组。

//ViewModel层
val mWanLiveData = MutableLiveData<List<WanModel>>()//UI层
@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
fun Greeting(vm: ComposeVModel = viewModel()) {//将 LiveData<T> 转换成 State<T>val liveDataState by vm.mWanLiveData.observeAsState()......
}

使用obseverAsState()需要引入:

implementation "androidx.compose.runtime:runtime-livedata:1.1.1"

注:谷歌建议务必在可组合项中使用 LiveData<T>.observeAsState() 等可组合扩展函数转换类型。

produceState 将对象转换为 State 状态

produceState 会启动一个协程,该协程将作用域限定为可将值推送到返回的 State 的组合。使用此协程将对象转换为 State 状态,例如将外部订阅驱动的状态(如 Flow、LiveData 或 RxJava)引入组合。

即使 produceState 创建了一个协程,它也可用于观察非挂起的数据源。如需移除对该数据源的订阅,请使用 awaitDispose 函数。

看一个官方的示例,展示了如何使用 produceState 从网络加载图像:

@Composable
fun loadNetworkImage(url: String,imageRepository: ImageRepository
): State<Result<Image>> {// Creates a State<T> with Result.Loading as initial value// If either `url` or `imageRepository` changes, the running producer// will cancel and will be re-launched with the new inputs.return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {// In a coroutine, can make suspend callsval image = imageRepository.load(url)// Update State with either an Error or Success result.// This will trigger a recomposition where this State is readvalue = if (image == null) {Result.Error} else {Result.Success(image)}}
}

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

MySQL实战1

文章目录 主要内容一.墨西哥和美国第三高峰1.准备工作代码如下&#xff08;示例&#xff09;: 2.目标3.实现代码如下&#xff08;示例&#xff09;: 4.相似例子代码如下&#xff08;示例&#xff09;: 二.用latest_event查找当前打开的页数1.准备工作代码如下&#xff08;示例&…

C++设计模式_20_Composite 组合模式

Composite 组合模式和后面谈到的Iterator&#xff0c;Chain of Resposibility都属于“数据结构”模式。Composite 组合模式核心是通过多态的递归调用解耦内部和外部的依赖关系。 文章目录 1. “数据结构”模式1.1 典型模式 2. 动机( Motivation )3. 模式定义4. Composite 组合模…

科普|电源自动测试系统测试的项目都有哪些?

电源自动测试系统是一种用于电源性能自动测试的集成系统&#xff0c;它可以自动检测电源模块或开关电源的输入、输出、保护等各个方面。该系统通常由数据软件和各类硬件测试仪器共同组成&#xff0c;利用通讯总线、测试夹具以及其它线缆等将仪器进行连接组成整体的系统结构&…

day14_集合

今日内容 零、 复习昨日 一、集合框架体系 二、Collection 三、泛型 四、迭代 五、List(ArrayList、LinkedList) 零、 复习 throw和throws什么区别 throwthrows位置方法里面方法签名上怎么写throw 异常对象throws异常类名(多个)作用真正抛出异常对象声明抛出的异常类型 运行时…

成本预算管理系统

成本预算管理系统 功能介绍&#xff1a; 一 基本信息&#xff1a; 1、产品设置&#xff1a;产品的长、宽、高及面积计算公式的设置。 2、板材设置&#xff1a;板材类别、厚度、尺寸的设置 3、系统名称&#xff1a;风管系统的类别设置 4、公司信息&#xff1a;本公司的信息…

【多线程】线程互斥 {竞态条件,互斥锁的基本用法,pthread_mutex系列函数,互斥锁的原理;死锁;可重入函数和线程安全}

一、进程线程间通信的相关概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。确切的说&#xff0c;临界资源在同一时刻只能被一个执行流访问。临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。互斥&#xff1a;通过互…

基于鸟群算法的无人机航迹规划-附代码

基于鸟群算法的无人机航迹规划 文章目录 基于鸟群算法的无人机航迹规划1.鸟群搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用鸟群算法来优化无人机航迹规划。 1.鸟群搜索算法 …

0基础学习PyFlink——用户自定义函数之UDF

大纲 标量函数入参并非表中一行&#xff08;Row&#xff09;入参是表中一行&#xff08;Row&#xff09;alias PyFlink中关于用户定义方法有&#xff1a; UDF&#xff1a;用户自定义函数。UDTF&#xff1a;用户自定义表值函数。UDAF&#xff1a;用户自定义聚合函数。UDTAF&…

vue2+ant-design-vue a-select组件二次封装(支持单选/多选添加全选/分页(多选跨页选中)/自定义label)

一、效果图 二、参数配置 1、代码示例 <t-antd-selectv-model"selectVlaue":optionSource"stepList"change"selectChange" />2、配置参数&#xff08;Attributes&#xff09;继承 a-select Attributes 参数说明类型默认值v-model绑定值…

vivado crash

将增量编译去了

FPGA时序分析与约束(9)——主时钟约束

一、时序约束 时序引擎能够正确分析4种时序路径的前提是&#xff0c;用户已经进行了正确的时序约束。时序约束本质上就是告知时序引擎一些进行时序分析所必要的信息&#xff0c;这些信息只能由用户主动告知&#xff0c;时序引擎对有些信息可以自动推断&#xff0c;但是推断得到…

Sprint Cloud Stream整合RocketMq和websocket实现消息发布订阅

1.引入RocketMQ依赖&#xff1a;首先&#xff0c;在pom.xml文件中添加RocketMQ的依赖&#xff1a; <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.0</versi…

文件改名,轻松添加前缀顺序编号,文件改名更高效!

您是否曾经需要批量修改文件名&#xff0c;并希望在文件名中添加特定的前缀或顺序编号&#xff1f;现在&#xff0c;我们为您带来了一款全新的文件改名工具&#xff0c;帮助您轻松解决这个问题&#xff01; 第一步&#xff0c;进入文件批量改名高手主页面&#xff0c;在板块栏…

C++学习笔记之四(标准库、标准模板库、vector类)

C 1、C标准库2、C标准模板库2.1、vector2.1.1、vector与array2.1.2、vector与函数对象2.1.3、vector与迭代器2.1.4、vector与算法 1、C标准库 C C C标准库指的是标准程序库( S t a n d a r d Standard Standard L i b a r a y Libaray Libaray)&#xff0c;它定义了十个大类…

亚马逊,速卖通,美客多如何打造爆款商品,排名提升榜首

1、产品Listing的完整性 Listing是亚马逊A9算法认识你产品的基础&#xff0c;在发布一条listing的时候&#xff0c;尽可能地做到最好!在准备一条listing之前&#xff0c;一定事先要收集、整理足够多的产品关键词&#xff0c;在优化listing内容的时候填充进去。仔细观察优秀竞品…

Realrek 2.5G交换机 8+1万兆光RTL8373-VB-CG方案简介

新一代2.5G交换机方案RTL8373-VB-CG可以提供4中不同形态 a. 52.5G 电口110G光》RTL8373 b. 52.5G 电口110G电》RTL83738261 c. 82.5G 电口110G光》RTL83738224 d.82.5G 电口110G电口》RTL837382248261 1.概述 Realtek RTL8373-CG是一款低功耗、高性能、高度集成的八端口2.5G和一…

C++设计模式_19_Memento 备忘录(理解,目前多使用序列化方案来实现)

Memento 备忘录模式也属于“状态变化”模式&#xff0c;它是一个小模式&#xff0c;在今天来看有些过时&#xff0c;当今已经很少使用当前模式实现需求&#xff0c;思想却不变&#xff08;信息隐藏&#xff09;&#xff0c;目前多使用序列化方案来实现。本系列所介绍的模式&…

小程序开发——小程序项目的配置与生命周期

1.app.json配置属性 app.json配置属性 2.页面配置 app的页面配置指的是pages属性&#xff0c; pages数组的第一个页面将默认作为小程序的启动页。利用开发工具新建页面时&#xff0c;则pages属性对应的数组将自动添加该页面的路径&#xff0c;若是在硬盘中添加文件的形式则不…

C++数据结构X篇_23_快速排序(最快、不稳定的排序)

文章参考十大经典排序算法-快速排序算法详解进行整理补充。快速排序是最快的排序方法。 排序思路&#xff1a;分治法-挖坑填数&#xff1a;大问题分解为各个小问题&#xff0c;对小问题求解&#xff0c;使得大问题得以解决 文章目录 1. 什么是快速排序1.1 概念1.2 算法原理1.3 …

Linux rm命令:删除文件或目录

当 Linux 系统使用很长时间之后&#xff0c;可能会有一些已经没用的文件&#xff08;即垃圾&#xff09;&#xff0c;这些文件不但会消耗宝贵的硬盘资源&#xff0c;还是降低系统的运行效率&#xff0c;因此需要及时地清理。 rm 是强大的删除命令&#xff0c;它可以永久性地删除…