当前位置: 首页 > news >正文

Android Jetpack Compose状态管理与状态提升

Compose 状态管理

什么是状态

Compose是声明式UI,UI的变化都是由数据的状态驱动的,在了解状态管理之前,先要了解为什么需要状态管理。
Compose界面的构成都是由一个个可组合函数构成的,当可组合函数的输入发生变化时,系统会重新执行这个可组合函数并重新构建UI,例如TextView显示文本,当文本内容发生变化时,随之UI也需要发生变化,下面是状态驱动UI变化的实现:

setContent {var text = "Hello World"StateTest(text) {text = "Hello Compose"}
}@Composable
fun StateTest(text: String, onClick: () -> Unit) {Text(text = text,color = Color.Black,modifier = Modifier.padding(10.dp).fillMaxWidth().clickable {onClick()})
}

以上代码想要的结果是当点击Text时,Text显示的文本发生改变,但是当我们点击时,发现文本内容并没有改变,此时就需要通过状态来控制Compose函数重组时的UI变化。此时就需要用到rememberSnapshotStateremember是保存一个表达式计算的值,这个值发生变化会导致使用这个值作为输入参数的Compose函数发生重组,snapshotState 是 Compose 状态管理系统的核心机制,它通过快照系统,允许Compose函数根据输入进行自动重组,不再是传统View中需要手动更新。将上面的代码实现为状态管理的方式更新UI,代码如下:

setContent {val text = "Hello World"var rememberText by remember {mutableStateOf(text)}StateTest(rememberText) {rememberText = "Hello Compose"}
}@Composable
fun StateTest(text: String, onClick: () -> Unit) {Text(text = text,color = Color.Black,modifier = Modifier.padding(10.dp).fillMaxWidth().clickable {onClick()})
}

此时,Text点击事件中,我们改变了rememberText的值,因为这个值是被remember记录的,会触发StateTest函数的重组,UI界面发生变化。

remember函数提供重载的函数有一个至多个参数,

@Composable
inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =currentComposer.cache(false, calculation)@Composable
inline fun <T> remember(key1: Any?,crossinline calculation: @DisallowComposableCalls () -> T
): T {return currentComposer.cache(currentComposer.changed(key1), calculation)
}

可以根据自己的需求传入不同的计算条件参与值的计算,从而根据缓存的值判断Compose函数在数据源发生变化时是否需要重组:
例如我们在Compose函数首次重组时,根据传入的List数据源计算值大于60的数据集合,并在列表项里将大于60的item背景色标记为红色,可以实现如下:

@Composable
fun ScoreFlagList(scoreList: MutableList<Int>) {val passScoreList = remember(scoreList) {scoreList.filter { it > 60 }.map { scoreList.indexOf(it) }.toSet()}Column {scoreList.forEach {Text(text = "score: $it",modifier = Modifier.fillMaxWidth().height(40.dp).background(color = if (passScoreList.contains(scoreList.indexOf(it))) {Color.Green} else {Color.Red}))}}
}
状态管理相关API与用法

Compose函数因为数据变化而发生重组必须是State类型的数据,并且被remember缓存在Compose函数的内存快照中,对于一些可变但不可被观察的数据,例如ArrayList、mutableListOf或可变数据类等,我们必须使用例如MutableState<ArrayList>的形式,让数据能被可组合函数观察到。同时Compose并不是强制要求MutableState存储状态,也可以将其他可观察的数据转为Compose可观察的状态,例如LiveData,Flow等

  • 将可变List集合转换为可观察对象
val list = mutableListOf(65, 76, 98, 86, 11, 74, 68, 72, 97, 91)
val stateList =  remember { mutableStateOf(list) }
  • 将LiveData转为状态变量
@Composable
fun LiveDataState(textString: MutableLiveData<Int>) {val liveData = textString.observeAsState()Text(text = "Number = ${liveData.value}", modifier = Modifier.fillMaxWidth().height(30.dp).clickable {textString.value = textString.value!! + 1})
}
  • 将Flow数据转换为State变量
@Composable
fun FlowDataState(textString: MutableStateFlow<Int>) {val liveData = textString.collectAsState(initial = 0)Text(text = "Number = ${liveData.value}", modifier = Modifier.fillMaxWidth().height(30.dp).clickable {textString.update { it + 1 }})
}

官方还提供了RxJava的数据变化时,转换为State的相关API,同时自定义的一些可观察的数据应该使用produceStateAPI来产生状态。
在这里插入图片描述

单向数据流

在传统的View体系中,假设我们需要实现一个Text用于实时显示输入框输入的内容,我们可以这样实现:

class MainActivity2 : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.editText.doAfterTextChanged { text ->binding.helloText.text = "hello, ${text.toString()}"}}
}

当输入框数据发生变化时,会回调doAfterTextChanged事件,然后将数据更新到TextView中,在复杂的场景中,一个事件导致一个UI的更新,同时也可能导致另一个UI的更新或者数据变化,这种状态变化称之为非结构化状态,在Android传统开发中,这种非结构化状态无法进行系统性的单元测试,View与事件处理与数据是混合在一起的。因此引入ViewModel和LiveData进行事件、数据和UI的分离。

class MainActivity2 : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate val inputTextViewModel by viewModels<InputTextViewModel>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.editText.doAfterTextChanged { text ->inputTextViewModel.onInputTextChange(text.toString())}inputTextViewModel.inputText.observe(this) {binding.helloText.text = it}}
}class InputTextViewModel : ViewModel() {private val _inputText = MutableLiveData<String>()val inputText: LiveData<String> = _inputTextfun onInputTextChange(text: String) {_inputText.value = text}
}

这样UI的事件流向ViewModel,ViewModel中改变数据,UI监听LiveData,LiveData数据被改变驱动UI更新,这种方式称之为单向数据流。
单向数据流的优点如下:

  • 可预测性与调试简化
  • 状态隔离与安全性
  • 可扩展性提升
状态提升

前面介绍了状态,状态更新一般由事件产生,常见的用户的输入事件,例如点击、长按,或者一些其他事件导致数据变化的事件,此时状态就应该更新,随之更新UI。而Compose最大的特点就是可组合的,我们写的空间要想拥有很强的复用性,我们就需要状态提升,状态提升就是把状态作为参数传入给可组合函数,状态的变化由调用方控制。

class ComposeLearnActivity : ComponentActivity() {private val inputTextViewModel by viewModels<InputTextViewModel2>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {InputTextPreviewComponent(inputText = inputTextViewModel.inputText, onInputTextChanged = {inputTextViewModel.onInputTextChange(it)})}}
}@Composable
fun InputTextPreviewComponent(inputText: State<String>, onInputTextChanged: (String) -> Unit) {Column {Text(text = inputText.value)TextField(value = inputText.value, onValueChange = onInputTextChanged)}
}class InputTextViewModel2 : ViewModel() {private val _inputText: MutableState<String> = mutableStateOf("")val inputText: State<String> = _inputTextfun onInputTextChange(text: String) {_inputText.value = text}
}

以上代码就是一个状态提升的实例,我们可以看到InputTextPreviewComponent函数内部只处理UI的显示和事件的回调,并没有操作ViewModel的数据,因此复用起来更方便。

状态保存与恢复

Activity在某些情况下可能会销毁重建,例如屏幕旋转没有正确配置configChanges设置,如果Activity销毁重建了,我们还需要保留之前的状态,就需要使用到rememberSaveable将状态保存到Bundle中,在重建的时候读取数据并恢复状态。先看一个不保存状态的例子:

class ComposeLearnActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)Log.d("Compose", "onCreate")setContent {val countNUm = remember { mutableStateOf(0) }TextCounter(countNUm, {countNUm.value++})}}override fun onDestroy() {super.onDestroy()Log.d("Compose", "onDestroy")}
}@Composable
fun TextCounter(countNUm: State<Int>, clickable: () -> Unit) {Row(modifier = Modifier.fillMaxWidth().fillMaxHeight(),verticalAlignment = Alignment.CenterVertically) {Text("当前计数:${countNUm.value}",modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp).background(Color(0xFFE0E0E0)).clickable { clickable() },textAlign = TextAlign.Center,fontSize = 15.sp,color = Color(0xFF000000),)}
}

Activity重建之后还会执行生命周期方法onCreate 此时TextCounter 会重组,此时的值还是默认值0。此时只需要将remember改为rememberSaveable ,销毁重建之后还是原来的值。

val countNUm = rememberSaveable{mutableStateOf(0)}

rememberSaveable会自动将基础类型的数据存储到Bundle中,如果你需要保存的数据是自定义类型的,比如数据类,你可以使用不同的存储机制,例如使用 Parcelize 注解、使用 listSavermapSaver 等 Compose API,或实现会扩展 Compose 运行时 Saver 类的自定义 Saver 类。
例如使用Parcelize注解:@kotlinx.parcelize.Parcelize是一个gradle插件,需要导入相关插件

@kotlinx.parcelize.Parcelize
data class City(var name: String, var score: Int) : Parcelable

在onCreate方法中,我们只需要使用rememberSaveable进行状态保存与恢复即可实现

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)Log.d("Compose", "onCreate")setContent {val city = rememberSaveable {mutableStateOf(City("上海", 82))}CityShow(city) {city.value = city.value.copy(score = city.value.score + 1)}}
}

还有一种方式就是使用Compose提供的Saver,可以看到官方已经实现了一些保存容器状态的Saver,我们要实现自己的自定义Saver可以参考官方的实现。官方文档地址

fun <Original, Saveable> listSaver(save: SaverScope.(value: Original) -> List<Saveable>,restore: (list: List<Saveable>) -> Original?
): Saver<Original, Any> =@Suppress("UNCHECKED_CAST")Saver(save = {val list = save(it)for (index in list.indices) {val item = list[index]if (item != null) {require(canBeSaved(item)) { "item can't be saved" }}}if (list.isNotEmpty()) ArrayList(list) else null},restore = restore as (Any) -> Original?)
http://www.dtcms.com/a/269758.html

相关文章:

  • linux安装CUDA
  • VM文件管理与Vi/vim操作
  • multicore和multithreading
  • 多模态交互HMI全解析:语音、手势、眼动追踪的集成方案
  • rocketmq 刷盘机制 与同步机制区别
  • JavaScript之数组方法详解
  • VSYNC 深度解析
  • Apollo源码架构解析---附C++代码设计示例
  • 提炼总结—ROS2机器人开发(完结)
  • 【WEB】Polar靶场 16-20题 详细笔记
  • Python实现二分查找算法详解
  • 经典论文 Science子刊:数据驱动的偏微分方程发现 —— Supplementary Materials
  • 找了两个月,没找到工作
  • 【笔记】开源 AI Agent 项目 V1 版本 [新版] 部署 日志
  • 开源 python 应用 开发(四)python文件和系统综合应用
  • go go go 出发咯 - go web开发入门系列(一) helloworld
  • uniapp使用 renderjs 多平台谷歌地图(Google Map)的适配
  • 力扣-31.下一个排列
  • React Native安卓刘海屏适配终极方案:仅需修改 AndroidManifest.xml!
  • 【openGLES】安卓端EGL的使用
  • Javafx教程(1)——初始Javafx
  • 工业HMI的智能化转型:边缘计算与预测性维护的深度融合
  • 自定义RecyclerView的ItemDecoration,用于处理网格布局间距装饰器(支持边缘间距独立控制)
  • ubuntu vscode 点击变量链接进去后 怎么返回原来的位置
  • LocalStorage和SessionStorage的区别和应用
  • 马尔可夫决策过程
  • python办公自动化----使用pandas和os合并多个订单表
  • 【python】 `parse_time_to_seconds` 在功能及健壮性上有以下主要区别
  • ​扣子Coze飞书多维表插件添加数据记录
  • 【UE5】虚幻引擎小百科