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

news2024/11/19 15:18:31

我们知道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}")//Logcat
       
    Column {
       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) : Parcelable

data 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 calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = 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.coloradmin.cn/o/1149965.html

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

相关文章

重磅!GPT-4又进化了!画图、插件、代码等能力被整合,超级智能体来了

夕小瑶科技说 原创 作者 | 小戏、ZenMoore 就在今天&#xff01;OpenAI 闷声放了一个大招&#xff01; 还没有官宣&#xff0c;还没有发布会&#xff0c;也没有大肆报道与关注。OpenAI 这次仅仅以灰度测试的方式&#xff0c;给部分用户发布了一个可以说“整合了几乎所有可用工…

CentOS 安装 Hadoop Local (Standalone) Mode 单机模式

CentOS 安装 Hadoop Local (Standalone) Mode 单机模式 Hadoop Local (Standalone) Mode 单机模式 1. 修改yum源 并升级内核和软件 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repoyum clean allyum makecacheyum -y update2. 安…

极智嘉(Geek+)携数款创新方案亮相CeMAT展会,再度展现硬核实力

近日&#xff0c;CeMAT ASIA 2023盛大召开&#xff0c;全球仓储机器人引领者极智嘉(Geek)携旗下数款创新方案亮相&#xff0c;再次向各界展示了其在前沿科技、产品打磨上的硬实力。 首先是货架到人PopPick方案搭配VSW自动播种墙 在传统货到人拣选作业中&#xff0c;拣选员约有…

converted from warning

converted from warning 关注微信&#xff1a;生信小博士 本地或者其它服务器跑同样的代码是正常的&#xff0c;只是有警告&#xff0c;但是在西柚云服务器上面运行会报错&#xff1f; 这是由于您两个环境使用的包版本不一样导致的&#xff0c;有如下解决方法 或者之前只是告警…

【23真题】知识点覆盖全!有罕见判断题!

今天分享的是23年烟台大学833的信号与系统试题及解析。 本套试卷难度分析&#xff1a;本套试题内容难度中等偏下&#xff0c;题目难度不大&#xff0c;但是题量较多&#xff0c;考察的知识点全面&#xff0c;比较多的考察了对信号波形以及频谱图的画法&#xff0c;值得注意的是…

路由vue-router

文章目录 一、前端路由的概念与原理1、 什么是路由2、SPA 与前端路由3、什么是前端路由4、前端路由的工作方式5、实现简易的前端路由 二、vue-router 的基本用法1、 什么是 vue-router2、 vue-router 安装和配置的步骤&#xff08;1&#xff09;在项目中安装 vue-router&#x…

Ceph入门到精通-恢复BlueStore中对象数据

1.基本原理介绍 1.1 ceph中的对象(object) 在Ceph存储中&#xff0c;一切数据最终都会以对象(Object)的形式存储在硬盘&#xff08;OSD&#xff09;上&#xff0c;每个的Object默认大小为4M。 通过rados命令&#xff0c;可以查看一个存储池中的所有object信息&#xff0c;例如…

SPI、IIC信号隔离

除了时钟信号SCK不用接上拉&#xff0c;其他信号都需要接10K上拉电阻到3.3V电源。 一、SPI信号隔离&#xff1a; 二、 IIC信号隔离

用例设计需遵循哪些规范标准?

不同的人因经验、方法、技巧不同&#xff0c;同样的用例编写质量差异大。 大家平时编写用例都有遵循哪些规范标准呢&#xff1f; 虽然是一名开发&#xff0c;但是我们做需求时也需要和测试一起进行用例评审&#xff0c;总结一下我们公司在进行用例设计时的标准。 我们公司要求用…

运大模型风起云涌,图解AI如何赋能产业升级?

导读 随着AI技术加速迭代&#xff0c;国内虚拟人市场规模呈现强势增长态势。据艾集咨询数居显示&#xff0c;2022年中国虚以人带动产业市场规模和核心市场规模预测值分别为1866,1亿元和120.8亿元呈现强劲的增长态势。随着数字化娱乐场景需求的增加&#xff0c;AI等技术不断送…

使用Swift模拟用户登录当网获取数据并保存到MySQL中

前言 当当网作为中国最大的综合性网上商城之一&#xff0c;通过爬取当当网数据&#xff0c;我们可以获取商品信息、用户评价、销售数据等宝贵的信息资源。这些数据可以帮助企业了解市场趋势、分析竞争对手、优化产品定价等&#xff0c;从而做出更明智的决策。 为什么使用Swif…

quartus+modesim仿真验证基本流程(使用自带仿真波形编辑器)

文章目录 环境搭建一、quartus设置二、quartus中新建工程三、仿真结果本文演示如何在quartus中启用modelsim进行功能仿真,同时重要一点是利用quartus中自带的仿真波形编辑工具,给输入信号通过图形界面生成想要的波形,之后调用modelsim进行仿真,将仿真结果直接显示在仿真波形…

synchronized总结:怎么保证可见性、有序性、原子性?

synchronized的原子性 synchronized 底层实际上通过JVM来实现的&#xff0c;同一时间只能有一个线程去执行synchronized 中的代码块。 原子性&#xff1a;既然同一时间只有一个线程去运行里面的代码&#xff0c;那么这个操作就是不能被其它线程打断的&#xff0c;所以这里天然…

语音芯片怎么录音 以及如何选择合适的录音芯片

一、语音芯片如何录音 语音芯片怎么录音 以及如何选择合适的录音芯片 语音芯片&#xff0c;其中就有一个品类&#xff0c;称之为录音芯片 其实他们是合并在一个芯片里面的&#xff0c;也就是说&#xff0c;录音芯片肯定是又可以录又可以播 但是能播放的语音芯片&#xff0c…

OA——菜单里无法找到流程监控

在门户引擎里面做查看菜单的权限设置 编辑里面的可查看权限管理 把权限给到对应的人员或者部门就可以

恒驰服务 | 华为云数据使能专家服务offering之数仓建设

恒驰大数据服务主要针对客户在进行智能数据迁移的过程中&#xff0c;存在业务停机、数据丢失、迁移周期紧张、运维成本高等问题&#xff0c;通过为客户提供迁移调研、方案设计、迁移实施、迁移验收等服务内容&#xff0c;支撑客户实现快速稳定上云&#xff0c;有效降低时间成本…

python的pytorch和torchvision利用wheel文件安装

python的pytorch和torchvision利用wheel文件安装 在做人工智能的时候&#xff0c;我们需要下载pytorch和torchvision&#xff0c;那么如何下载呢。利用wheel文件pip安装 下载 首先要看你的python版本&#xff0c;打开命令行&#xff0c;输入&#xff1a; python -V就可以看…

ip划分与私公网ip、ip的传递

报文问路&#xff1a;1、不知道跳转默认路由器&#xff0c;2、知道路径&#xff0c;向对应路径发出报文&#xff0c;3、路口路由器&#xff0c;下一步就是目标主机在哪。 想要通信必须同在一个局域网&#xff0c;其实将公网就可以看作一个大型的局域网。 在同一个局域网内发送…

制药、饮料等流程制造业主数据如何管?

业务背景——制造行业的数据转型过程中&#xff0c;主数据管理是成功的必要条件 MASTER DATA MANAGEMENT 流程制造是指被加工对像不间断地通过生产设备&#xff0c;通过一系列的加工装置使原材料进行化学或物理变化&#xff0c;最终得到产品。 由于流程制造中物料的变动性强&a…

C++ 写一个Data类的注意问题

Data类 声明和定义分离的一些问题 声明里面我们不带缺省参数&#xff0c;定义我们给缺省参数&#xff0c;如下面两段代码&#xff1a; Data.h#pragma once #include<iostream> using namespace std; class Data { public:Data(int year,int month,int day);private:in…