Compose 状态管理

文章目录

  • Compose 状态管理
    • 概述
    • 使用
      • MutableState
      • remember
      • StatelessComposable & StatefulComposable
      • 状态提升
      • rememberSaveable
        • 支持parceable
        • 不支持parceable
    • 使用ViewModel
      • ViewModelProvider.Factory
    • 使用Flow

Compose 状态管理

概述

当应用程序的状态发生变化时,Compose会进行重组,重组过程中会运行可能已更改的可组合项以响应状态变化,然后Compose会更新组合以反映所有更改。这就是Compose的工作流程。

使用

MutableState

val counter: MutableState<Int> = mutableStateOf(0)

这里的 counter 是一个MutableState<Int>类型,可以使用 .value 进行读写。

// 解构
val (counter: Int, setCounter: (Int) -> Unit) = mutableStateOf(0)

这里的 counter 是一个 Int 类型的数据,其他地方可以直接访问,需要更新 counter 时可以使用 setCounter(xx) 完成。

// 属性代理
val counter: Int by mutableStateOf(0)

这里的 counter 的读写会通过 getValue 和 setValue这 两个运算符的重写最终代理为对 value 的操作,通过 by 关键字,可以像访问一个普通的Int变量一样对状态进行读写。

remember

  • mutableStateOf() 函数会创建一个 MutableState 类型的对象,MutableState 是可观察类型,在值发生变化的情况下系统会安排重组持有该值的可组合函数。
  • remember() 在Composable首次执行时,remember中计算得到的数据会自动缓存,当Composable重组再次执行到remember处会返回之前已缓存的数据,无须重新计算。
@Preview
@Composable
fun Test3() {val index = remember {mutableStateOf(10)}Column(modifier = Modifier.fillMaxSize()) {Button(onClick = {index.value++Log.e("TAG", "点击了 :${index.value}")}) {Text("Click")}Text("${index.value}", fontSize = 30.sp)}
}

说明:使用 remember { mutableStateOf() } 记录状态,当 index 发生变化时,Text显示的值也会跟着发生变化。

StatelessComposable & StatefulComposable

StatelessComposable 不管理任何状态,它的输出仅取决于输入参数。它是无状态的,每次调用都会重新计算输出,并且不会记住之前的状态。

例如,一个简单的文本显示组件可以是一个 StatelessComposable

@Composable
fun MyTextComponent(text: String) { // StatelessComposableText(text = text)
}

说明:MyTextComponent 是一个 StatelessComposable,它接受一个字符串参数 text,并将其显示为文本。

StatefulComposable 会管理状态,内部持有或访问状态,并根据状态的变化来更新输出。

例如,一个计数器组件可以是一个 StatefulComposable

@Composable
fun MyCounterComponent() { // StatefulComposablevar count by remember { mutableStateOf(0) }Button(onClick = { count++ }) {Text(text = "计数器:$count")}
}

说明:MyCounterComponent 是一个 StatefulComposable,它内部使用 mutableStateOf 来管理一个整数状态 count。当点击按钮时,count 会增加 1,并更新计数器的显示。

StatelessComposable 更简单、高效,适用于不需要管理状态的组件,Stateless是一个“纯函数”,参数是变化的唯一来源,参数不变UI就不会变化。因此Compose编译器针对其进行了优化;而 StatefulComposable 更灵活、强大,适用于需要管理状态的组件。StatelesComposable 的重组只能来自上层 Composable 的调用,而 StatefulComposable 的重组来自其依赖状态的变化。

状态提升

状态提升也就是将 Statefule 改造为 Stateless,Stateless 由于不耦合任何业务逻辑,所以功能更加纯粹,相对于 Stateful 的可复用性更好,对测试也更加友好。

在这里插入图片描述

// StatefulComposable
@Composable
fun CounterScreen() {var counter by remember { mutableStateOf(0) }CounterComponent(counter = counter,onIncrement = { counter++ },onDecrement = {if (counter > 0) {counter--}})
}// StatelessComposable
@Composable
fun CounterComponent(counter: Int,onIncrement: () -> Unit,onDecrement: () -> Unit,
) {Column(modifier = Modifier.padding(16.dp)) {Text("Counter:", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)Text("$counter",modifier = Modifier.fillMaxWidth(),textAlign = TextAlign.Center,fontWeight = FontWeight.Bold)Row {Button(onClick = { onDecrement() }, modifier = Modifier.weight(1F)) {Text("-")}Button(onClick = { onIncrement() }, modifier = Modifier.weight(1F)) {Text("+")}}}
}

说明:CounterComponent经状态上提后,职责更加单一。

rememberSaveable

rememberSaveable() 函数可以保证ConfigurationChanged事件发生时(如屏幕旋转等)保存状态,数据会随onSaveInstanceState进行保存。

并在进程或者Activity重建时根据key恢复到对应的Composable中,这个key就是Composable在编译期被确定的唯一标识。因此当用户手动退出应用时,rememberSavable中的数据才会被清空。

rememberSavable实现原理实际上就是将数据以Bundle的形式保存,所以凡是Bundle支持的基本数据类型都可以自动保存。对于一个对象类型,则可以通过添加@Parcelize变为一个Parcelable对象进行保存。

支持parceable

添加kotlin-parcelize插件:

plugins {id 'kotlin-parcelize'
}

使用:

// 定义数据类
@Parcelize
data class Person(val name: String, val age: Int) : Parcelable// 使用:
@Composable
fun PersonScreen() {var person by rememberSaveable { mutableStateOf(Person("小白", 18)) }Button(onClick = { person = Person("小黑", 28) }) {Text(person.toString())}
}
不支持parceable

有的数据结构可能无法添加Parcelable接口,比如定义在三方库的类等,此时可以通过自定义Saver为其实现保存和恢复的逻辑。只需要在调用rememberSavable时传入此Saver。

// 定义数据类
data class People(val name: String, val age: Int)// 定义Saver
object PersonSaver : Saver<People, Bundle> {override fun restore(value: Bundle): People {val name = value.getString("name") ?: ""val age = value.getInt("age")return People(name, age)}override fun SaverScope.save(value: People): Bundle {return Bundle().apply {putString("name", value.name)putInt("age", value.age)}}
}// 使用:
@Composable
fun PeopleScreen() {var people by rememberSaveable(stateSaver = PersonSaver) { mutableStateOf(People("小红", 18)) }Button(onClick = { people = People("小绿", 28) }) {Text(people.toString())}
}

支持MapSaver:

MapSaver将对象转换为 Map<String, Any> 的结构进行保存。

data class City(val name: String, val country: String)// 定义MapSaver
val CitySaver = run {val nameKey = "name"val countryKey = "country"mapSaver(save = { mapOf(nameKey to it.name, countryKey to it.country) },restore = { City(it[nameKey] as String, it[countryKey] as String) })
}@Composable
fun CityScreen() {var city by rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("北京", "中国")) }Button(onClick = { city = City("东京", "日本") }) {Text(city.toString())}
}

支持ListSaver:

ListSaver则是将对象转换为 List<Any> 的数据结构进行保存。

val CitySaver2 = listSaver<City, Any>(save = { listOf(it.name, it.country) },restore = { City(it[0] as String, it[1] as String) }
)

使用ViewModel

在 Compose 中也可以使用 ViewModel 缓存状态,通过 LiveData 或 Flow 监听变化。

添加依赖:

implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"

使用:

class CountViewModel : ViewModel() {private val _count = MutableLiveData(10)val count get() = _countfun onCountChanged(count: Int) {_count.postValue(count)}
}
@Composable
fun MyCount() {val viewModel: CountViewModel = viewModel()val count by viewModel.count.observeAsState(0) // 语法糖Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text("$count")Button(onClick = { viewModel.onCountChanged(count + 2) }) {Text("点击")}}
}

说明:通过 observeAsState() 函数观察 LiveData 对象,当 LiveData 发生变化时,该对象都会更新。

ViewModelProvider.Factory

class CountViewModel(defaultCount: Int) : ViewModel() {private val _count = MutableLiveData(defaultCount)val count get() = _countfun onCountChanged(count: Int) {_count.postValue(count)}
}class CountViewModelFactory(private val defaultCount: Int) : ViewModelProvider.Factory {override fun <T : ViewModel> create(modelClass: Class<T>): T {return CountViewModel(defaultCount) as T}
}
@Composable
fun MyCount() {val viewModel: CountViewModel = viewModel(factory = CountViewModelFactory(100))val count by viewModel.count.observeAsState(0)Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text("$count")Button(onClick = {viewModel.onCountChanged( count+ 2) }) {Text("点击")}}
}

使用Flow

class CountViewModel : ViewModel() {private val _countFlow = MutableStateFlow(10)val countFlow get() = _countFlow.asStateFlow()fun onCountChanged(count: Int) {_countFlow.value = count}
}
@Composable
fun MyCount() {val viewModel: CountViewModel = viewModel()val count: Int by viewModel.countFlow.collectAsState(0) // 语法糖Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Text("$count")Button(onClick = {viewModel.onCountChanged(count + 2)}) {Text("点击")}}
}

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

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

相关文章

第五章 TypeScript泛型的介绍和使用

文章目录 一、泛型初识泛型泛型用法 二、断言 一、泛型 初识泛型 一个函数&#xff0c;需要参数是 number 数据类型&#xff0c;返回值也是 number 数据类型 function fn(arg: number): number { // 代码忽略不计 }又一个函数&#xff0c;需要参数是 string 类型&#xff0…

docker安装Debian:11 freeswitch1.10.5

文章目录 一、生成一个镜像二、切换一个镜像源为阿里源三、安装一些相关依赖和freeswitch3.1第一步&#xff1a;安装freeswitch-mod和下载所需的依赖项3.2 设置密钥3.3 安装freeswitch所需的依赖项3.4 报错3.4.1 报错13.4.2 报错23.4.3 报错3 四、运行4.1 通话三十秒自动挂断 一…

Linux 第二十二章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

嵌入式学习<1>:建立工程、GPIO

嵌入式学习_part1 本部分笔记用于学习记录&#xff0c;笔记源头 >>b站江科大_STM32入门教程 建立工程、GPIO 开发环境&#xff1a;keil MDK、STM32F103C8T6 1 &#xff09;建立工程 &#xff08;1&#xff09;基于寄存器开发、基于标准库 或者 基于HAL库开发; &…

10种软件架构模式解析

1. 单体应用架构&#xff08;Monolithic Architecture&#xff09; &#x1f44c;单体应用架构是最基本的架构模式&#xff0c;它将整个应用作为一个单独的部署单元。所有功能和模块都集成在一个应用中&#xff0c;易于开发和部署&#xff0c;但随着应用的增长&#xff0c;可维…

java.lang.NoSuchMethodException: com.ruoyi.web.controller.test.bean.HeadTeacher

软件开发过程中使用Java反射机制时遇到了下面的问题 com.ruoyi.web.controller.test.bean.HeadTeacher4b9af9a9 com.ruoyi.web.controller.test.bean.HeadTeacher4b9af9a9java.lang.NoSuchMethodException: com.ruoyi.web.controller.test.bean.HeadTeacher.<init>(java…

python判断大图中包含小图并输出位置总结

python判断大图中包含小图并输出位置总结 没啥可说的&#xff0c;项目遇到了就直接上代码&#xff0c;可以减轻劳动力&#xff0c;花最少得时间实现应用功能。 import cv2 # 读取大图片和小图片的路径 img_big cv2.imread(big_image.png) img_small cv2.imread(small_image…

【方法】如何创建RAR格式压缩文件?

为了方便存储或者传输文件&#xff0c;我们经常会把文件打包成不同格式的压缩包&#xff0c;那如果想创建的是RAR格式的压缩包&#xff0c;要如何做呢&#xff1f; RAR是WinRAR软件独有的压缩格式&#xff0c;所以我们可以通过WinRAR软件来创建RAR格式压缩包。下面分享两种创建…

视频素材哪个app好?8个视频素材库免费使用

视频内容已成为现代传播中不可或缺的一部分&#xff0c;具备卓越的视频素材对于提升任何媒体作品的质量和吸引力尤为关键。这里列举的一系列精挑细选的全球视频素材网站&#xff0c;旨在为您的商业广告、社交媒体更新或任何其他类型的视觉项目提供最佳支持。 1. 蛙学府&#x…

FileLink跨网文件交换,推动企业高效协作|半导体行业解决方案

随着信息技术的迅猛发展&#xff0c;全球信息产业已经迎来了前所未有的繁荣与变革。在这场科技革命中&#xff0c;半导体作为信息产业的基础与核心&#xff0c;其重要性日益凸显&#xff0c;半导体的应用场景和市场需求将进一步扩大。 然而&#xff0c;在这一繁荣的背后&#x…

OceanBase学习1:分布式数据库与集中式数据库的差异

目录 1. 传统集中式数据库 2. 数据库中间件的分库分表 3. 分布式数据库的基本特点及对比分析 4. OceanBase和传统数据库的对比 5. 小结 1. 传统集中式数据库 优点 成熟稳定:经过近40年的发展&#xff0c;应用到各行各业&#xff0c;产品技术非常成熟稳定行业适配性强:适配…

微软开发新模型;YouTube 推出新AI功能;可折叠iPhone 或发布?

微软或开发新模型与 Google、OpenAI 竞争 The Information 报道&#xff0c;微软正在训练一种新的 AI 大模型「MAI-1」&#xff0c;规模上足以与 Google、Anthropic 乃至 OpenAI 的先进模型抗衡。 据报道&#xff0c;这个 MAI-1 模型由微软聘请的 Inflection 前 CEO Mustafa S…

notepad++安装 hex-editor插件

打开notepad 点击插件 搜索 hex-editor,点击右侧 安装install 安装成功后&#xff0c;在已安装插件中就有显示了

分布式任务调度工具 XXL-JOB

默认的账号密码是&#xff1a;admin/123456 一&#xff0c;部署docker容器 docker run \ -e PARAMS"--spring.datasource.urljdbc:mysql://192.168.150.101:3306/xxl_job?Unicodetrue&characterEncodingUTF-8 \ --spring.datasource.usernameroot \ --spring.dataso…

百度副总裁秒批离职,00后的职场逆袭?

“员工闹分手提离职我秒批”&#xff0c;百度副总裁璩静的职场经历和思考在近期引发了大量讨论。 璩静在小红书分享了自己作为女性管理者&#xff0c;面对团队内部的感情问题&#xff0c;是如何处理的&#xff1a; “我第一时间就跟这个年轻的女孩子说&#xff0c;你走吧&…

Llama3-Tutorial之XTuner微调Llama3图片理解多模态

Llama3-Tutorial之XTuner微调Llama3图片理解多模态 基于 Llama3-8B-Instruct 和 XTuner 团队预训练好的 Image Projector 微调自己的多模态图文理解模型 LLaVA。 参考&#xff1a; https://github.com/SmartFlowAI/Llama3-Tutorial 1. 环境、模型、数据准备 1.1 配置环境 使用如…

【Delphi7】Access violation at address 0019F7C3. Write of address 0019F7C3.

这里写目录标题 问题基本情况问题描述1、启动Delphi 开发程序 时连续报如下错误2、打开“工程”菜单下的“选项”页面时时连续报如下错误 解决方案1、打开“高级系统设置”2、打开“性能选项”3、添加“数据执行保护”的程序4、选择“数据执行保护”的程序5、应用“数据执行保护…

从项目开始学习Vue——02(若依框架)

往期&#xff1a; 从项目开始学习Vue——01 目录标题 一、基础插件&#xff08;一&#xff09;路由Vue Router&#xff08;二&#xff09;导航守卫&#xff08;路由拦截器&#xff09;二、Vuex&#xff08;一&#xff09;什么是VuexVuex的部分介绍内容&#xff1a; &#xff08…

【数据分享】2022年中国1km分辨率的河网密度数据(免费获取)

水系数据是我们在各项研究中经常使用的数据&#xff01;之前我们分享过一份来源于Open Street Map的2024年全国水系数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff01; 本次我们分享一份来源于Science Data Bank平台上的2022年中国1km分辨率的河网密度数据&am…

【正点原子Linux连载】 第四十章 Linux网络驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1&#xff09;实验平台&#xff1a;正点原子ATK-DLRK3568开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id731866264428 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第四十…