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

news/2025/2/12 22:07:10/

我们知道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.ppmy.cn/news/1186160.html

相关文章

Transformer模型原理

NLP预训练模型的架构大致可以分为三类&#xff1a; 1. Encoder-Decoder架构&#xff08;T5&#xff09;&#xff0c;seq2seq模型&#xff0c;RNN、LSTM网络 2. BERT&#xff1a;自编码语言模型&#xff0c;预测文本随机掩码 3. GPT&#xff1a; 自回归语言模型&#xff0c;预测…

国产芯片vs“国际水平”,有距离也有超越!

当前&#xff0c;国产芯片正在迎来全新的发展阶段。国产终端芯片性能怎么样&#xff0c;与国际主流产品相比&#xff0c;表现如何&#xff1f;今天笔者就针对目前热度较高的四款国产CPU进行参数分析与性能跑分横向对比。 此次国产芯片评测型号分别是海光C86-3250、龙芯3A5000H…

原子类:Java并发编程的利器

在多线程环境下&#xff0c;确保数据的一致性和原子性是至关重要的。Java提供了一些原子类&#xff0c;用于解决多线程并发问题。这些原子类能够确保操作在多线程环境下是原子的&#xff0c;即不会被其他线程干扰。本文将介绍Java中的原子类及其应用。 一、原子类概述 原子类…

数据结构(三):栈及面试常考的算法

一、栈介绍 1、定义 栈也是一种数据呈线性排列的数据结构&#xff0c;不过在这种结构中&#xff0c;我们只能访问最新添加的数据。从栈顶放入元素的操作叫入栈&#xff0c;取出元素叫出栈。 2、优缺点及使用场景 优点&#xff1a;高效的操作、简单易用、空间效率高等 缺点&…

7.Vue2-循环语句的用法

题记 vue2循环语句的用法 v-for 绑定数组 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>实例</title> <script src"https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> <…

MySQL安装后,同局域网其他电脑无法连接问题

MySQL安装后&#xff0c;同局域网其他电脑无法连接问题 1、问题&#xff1a; 在同一个局域网下&#xff0c;笔者电脑安装了MySQL数据库后&#xff0c;出现了其他伙伴想连接笔者电脑的MySQL&#xff0c;而连不上的问题。 2、解决方案 在防火墙中添加开放端口&#xff1a; 第…

目标检测算法发展史

前言 比起图像识别&#xff0c;现在图片生成技术要更加具有吸引力&#xff0c;但是要步入AIGC技术领域&#xff0c;首先不推荐一上来就接触那些已经成熟闭源的包装好了再提供给你的接口网站&#xff0c;会使用别人的模型生成一些图片就能叫自己会AIGC了吗&#xff1f;那样真正…

Kafka生产问题总结及性能优化实践

Kafka可视化管理工具kafka-manager 安装及基本使用可参考&#xff1a;https://www.cnblogs.com/dadonggg/p/8205302.html 线上环境规划 JVM参数设置 kafka是scala语言开发&#xff0c;运行在JVM上&#xff0c;需要对JVM参数合理设置&#xff0c;参看JVM调优专题 修改bin/kaf…