Compose 状态管理

news2024/11/24 21:08:59

文章目录

  • 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) { // StatelessComposable
    Text(text = text)
}

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

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

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

@Composable
fun MyCounterComponent() { // StatefulComposable
    var 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() = _count

    fun 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() = _count

    fun 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://www.coloradmin.cn/o/1649174.html

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

相关文章

2024.5.7

槽函数声明 private slots:void on_ed_textChanged();void on_pushButton_clicked(); }; 槽函数定义 void Widget::on_ed_textChanged()//文本框 {if(ui->ed1->text().length()>5&&ui->ed2->text().length()>5){ui->pushButton->setStyleSh…

纯血鸿蒙APP实战开发——手写绘制及保存图片

介绍 本示例使用drawing库的Pen和Path结合NodeContainer组件实现手写绘制功能。手写板上完成绘制后&#xff0c;通过调用image库的packToFile和packing接口将手写板的绘制内容保存为图片&#xff0c;并将图片文件保存在应用沙箱路径中。 效果图预览 使用说明 在虚线区域手写…

二进制转为HEX数组小工具

在使用RA8889时&#xff0c;JPG的解码只能从FLASH的DMA通道获取&#xff0c;那么如果要从远端、或者SD卡等处读取JPG图片出来显示怎么办&#xff1f; RA8889支持JPG图片硬解码&#xff0c;但数据流是从FLASH进行DMA读取的&#xff0c;然后再进行解码。因此这种情况下&#xff…

音频数字信号I2S一些知识理解

(1)I2S单向基本传输需要几根线传输音频信号? 3根线 LRCK SCLK(也叫BLK) DATA(单向) (2)如何理解I2S MASTER或者SLAVE的模式&#xff1f; codec的i2s作为slave mode,LRCK和SCLK来自于soc主控端,codec端自动检测MCLK和LRCK codec的i2s作为master mode,codec通过MCLK LRCLKDIV…

使用Simulink Test进行单元测试

本文摘要&#xff1a;主要介绍如何利用Simulink Test工具箱&#xff0c;对模型进行单元测试。内容包括&#xff0c;如何创建Test Harness模型&#xff0c;如何自动生成excel格式的测试用例模板来创建测试用例&#xff0c;如何手动填写excel格式的测试用例模板来手动创建测试用例…

springboot模块以及非springboot模块构成的多模块maven项目最佳构建方式

文章目录 背景一般的实现使用spring-boot-dependencies 更优雅的实现. 背景 有时候构建一个多模块maven项目其中某一个模块是web-service需要使用spring boot,其他模块跟spring boot 完全无关,本文总结一下在这个场景下maven项目最佳构建方式. 一般的实现 网上应该也看到过很…

notepad++安装 hex-editor插件

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

LT6911GXC HDMI2.1转mipi / lvds 支持8K 60HZ,提供技术支持

一、 HDMI 以及 GPIO 使用的相关配置 1.1 配置 EDID 在 LT6911GX.c 文件中添加需要的 EDID 二、输出信号配置 2.1 选择输出信号格式 在 Global.h 中配置输出信号类型&#xff0c;如下图所示 2.2 MIPI 信号格式配置 在 MIPITXGlobal.h 中配置 MIPI 相关参数

Linux--基础IO(文件描述符fd)

目录 1.回顾一下文件 2.理解文件 下面就是系统调用的文件操作 文件描述符fd&#xff0c;fd的本质是什么&#xff1f; 读写文件与内核级缓存区的关系 据上理论我们就可以知道&#xff1a;open在干什么 3.理解Linux一切皆文件 4.C语言中的FILE* 1.回顾一下文件 先来段代码…

Python扩展模块的开发

有关python C扩展开发的教程可以参考概述 — Python 3.12.3 文档。项目已经发布至python官方的pypi里了。具体详情请见AdroitFisherman PyPI。目前该项目还处在测试阶段。尚有部分模块需要开发和测试。 项目结构 项目结构见下图&#xff1a; 代码展示与说明 以单链表(SingleL…

uniapp video 层级覆盖

层级覆盖 cover-view组件 我这里做了个判断 监听全屏时隐藏按钮 根据项目需求自行更改

KIE关键信息抽取——SDMG-R

https://arxiv.org/pdf/2103.14470https://arxiv.org/pdf/2103.14470 1.概述 背景:传统的关键信息提取方法依赖于模板匹配,这使它们难以泛化到未见过的模板,且对文本识别错误不够鲁棒。SDMG-R方法:提出一种端到端的双模态图推理方法,通过构建双模态图(视觉和文本特征),…

Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 前言 通过前三篇Sprin…

速看!这次主食冻干评测极可能被商家恶意举报~VE、希喂、PR真实测评

我发现还是有不少铲屎官局限于“进口最高贵”&#xff0c;盲目的迷信进口产品。看到进口粮就盲买&#xff0c;甚至过分的贬低国产品牌&#xff0c;将国产粮贴上“不靠谱”“不合格”等标签。 最近&#xff0c;我针对主食冻干的国内、国际标准&#xff0c;相关规范文件&#xf…

小红书餐饮推广怎么合作?纯干货

小红书作为国内领先的生活方式分享平台&#xff0c;其用户群体主要集中在一二线城市&#xff0c;年龄分布在18-35岁之间&#xff0c;其中女性用户占比高达80%。这部分用户具有较高的消费能力、审美追求和品质生活需求&#xff0c;对美食有着极高的兴趣和消费意愿&#xff0c;为…

数据结构---动态数组

一、数据结构基本理论 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。强调数据元素之间的关系 算法五个特性&#xff1a; 输入、输出、有穷、确定、可行 数据结构分类&#xff1a; 逻辑结构&#xff1a;集合、线性结构、树形结构、图形结构 物理…

中国居民消费新特征:中枢回落,即时满足,去地产化

随着收入预期和财富效应的转变&#xff0c;居民更倾向于通过短期集中式的消费来获得即时满足的快乐&#xff0c;服务消费表现出了更强的韧性。服务消费强于商品消费、消费去地产化、汽车挑大梁的特征延续。 特征一&#xff1a;消费倾向高于2020-22年&#xff0c;低于2017-19年…

zabbix监控方式(zabbix-trapper)

中文&#xff1a;zabbix采集器&#xff0c;即zabbix sender 。 Zabbix-Trapper 监控方式可以一次批量发送数据给Zabbix Server&#xff0c;与主动模式不同&#xff0c;Zabbix-Trapper 可以让用户控制数据的发送&#xff0c;而不用Zabbix-Agent进程控制&#xff0c;这意味着可以…

数据挖掘算法原理与实践:决策树

第2关&#xff1a;决策树算法原理 任务描述 本关任务&#xff1a;根据本关所学知识&#xff0c;完成 calcInfoGain 函数。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 信息熵&#xff1b;条件熵&#xff1b;信息增益。 信息熵 信息是个很抽象的概念。…