原文地址: Jetpack Compose学习(8)——State状态及remeber关键字 - Stars-One的杂货小窝

之前我们使用TextField,使用到了两个关键字remembermutableStateOf,这两个是做什么用的呢?本篇特来补充说明下

mutableStateOf

之前也说过,compose是MVVM模式的一种实现,UI界面依赖数据,数据改变即改变UI

这里需要去监听数据,当数据发生改变才会触发UI渲染,改变UI

Android官方将上面这种情况称之为重组,我个人理解觉得重新渲染这个词更好说明

由于数据变化监听逻辑复杂,显然不应该由我们开发者去完成,所以Android官方特地封装好了相应的类供我们使用,便于快速开发,于是就是轮到今天的主角State

从官方的文档说明,State是一个接口,MutableState则是实现了State的一个接口

我们只需要每次创建MutableState对象使用即可,而创建对象的方法Android官方团队也是为我们提供了一个方法,即mutableStateOf()

fun <T : Any?> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T>

本质上,mutableStateOf()方法相当于提供了包装类的功能,里面可包装任意类型的数值,这里为了方便下文讲解,将T称为数值类型

此方法接收两个参数,valuepolicy

  • value 任意数值
  • policy 策略,有三种选择,分别为referentialEqualityPolicy structuralEqualityPolicy neverEqualPolicy

三种的策略说明如下:

  • referentialEqualityPolicy 引用相等策略
  • structuralEqualityPolicy 数值相等策略(默认)
  • neverEqualPolicy 从不相等策略

上面也是说了,当数据发生改变才会触发重组,那么,怎么样才算数据发生改变呢?于是便是有了以上的三种选择

  • 引用相等策略:当数值类型T为对象,State设置新的对象,若是引用相等,则不会触发重组
  • 数值相等策略:当数值类型T为基本数据类型,State设置新数值,若是数值相等,则不会触发重组
  • 从不相等策略:State设置新的对象,不管引用或数值是否相等,一定会会触发重组

个人觉得引用相等和数值相等策略主要是为了优化频繁触发重组带来的性能问题,当然,也提供了一个不优化的选项(从不相等策略)

remember

使用remember和上述的mutableStateOf方法,我们可以创建一个变量,此变量如果是在compose中使用,更改变量数值即可达到更改UI的作用

//mutableState是State<String>对象
val mutableState = remember { mutableStateOf("") } //value是String对象
var value by remember { mutableStateOf("") }

之前一文Kotlin学习快速入门(8)—— 属性委托 - Stars-One的杂货小窝,也是讲解了Kotlin中的委托功能,这里的by就是使用了委托,value的get和set方法委托给了State<String>

所以我们直接对value调用get和set方法,其实都是在调用State<String>对象的get和set方法

将某些状态转为State

在compose中,某些类提供有转为State的方法,方便我们开发时候将其与UI绑定,从而实现动态改变UI的效果

如之前有讲解到的按钮(Button)的按压状态MutableInteractionSource,提供了三个方法来获取不同的State

  • collectIsPressedAsState 按压状态
  • collectIsDraggedAsState 拖动状态
  • collectIsFocusedAsState 焦点状态

除此之外,Compose也是考虑到了之前LiveData组件使用的数据类型,并给予一个扩展方法,可以获得其的State类

需要引用依赖库

implementation androidx.compose.runtime:runtime-livedata:$latestVersion

observeAsState方法 该方法的作用就是将ViewModel提供的LiveData数据转换为Compose需要的State数据

关于LiveData的使用,可以参考这一篇Jetpack架构组件学习(2)——ViewModel和Livedata使用 - Stars-One的杂货小窝

LiveData库中主要是两个类MutableLiveDataLiveData,使用observeAsState即可转为compose中的State对象,如下面例子:

@Composable
fun LoginPageDemo() { //这里只是演示用,实际情况MutableLiveData对象是要从ViewModel中取值的!
val mlivedate = MutableLiveData("myusername") //name为State<String>对象,只能读数值,不能修改数值
var name = mlivedate.observeAsState(initial = "") Column() {
val focusManager = LocalFocusManager.current
TextField(
modifier = Modifier.fillMaxWidth(),
value = name.value,
placeholder = {
Text("请输入用户名")
},
//注意这里,修改数值要去修改MutableLiveData对象
onValueChange = { str -> mlivedate.value = str}
)
}
}

PS:上面的例子比较简单,一般LiveData是与ViewModel联用的,MutableLiveData对象是要从ViewModel中取值的!

除了上面的LiveData,还存在响应式框架RxJava,Flow,也存在对应的转为State的方法,但笔者用的还比较少,所以这方面各位朋友需要自己找些资料了

rememberSaveable

使用remember有个注意点,就是其是对应的生命周期是属于Composable,当Composable被移除的时候(如出现屏幕方向由竖屏转为横屏),remember记住的数值也会丢失

这个时候,推荐使用的则是rememberSaveable

rememberSaveable会在Activity回调configChanges()的时候,将remember的值写入到bundle中,然后重新构建Activity的时候,从bundle读数据

这个关键字就是针对Activity出现重建的情况下进行数值的保存,不会丢失数值,使用方法也是与remember类似

val name = rememberSaveable {
mutableStateOf("")
}

状态提升

无状态可组合项: 没有状态的可组合项,这意味着它不会保存、定义或修改新状态。

有状态可组合项: 具有可以随时间变化的状态的可组合项。

简单来说,组合中使用 rememberrememberSaveState 方法保存状态的组合项是有状态组合,没有则是无状态组合。

一般如果涉及到组合的复用,则需要我们将组合由有状态组合提取为无状态组合,这就叫做状态提升

当一个组合函数,引入了下面两个参数,即可说此组合其属于有状态组合

  • value: T 形参,即要显示的当前值。
  • onValueChange: (T) -> Unit - 回调 lambda,会在值更改时触发,以便可以在其他位置更新状态(例如,当用户在文本框中输入一些文本时)

那么什么时候需要使用到状态提升呢?主要有下面两个情况:

  • 与多个可组合函数(Composable)共享状态。
  • 创建可在应用中重复使用的无状态可组合项。

下面以之前登录页的输入框为例,有两个输入框,一个是输入用户名,另外一个则是输入密码,但是我们直接是在里面写了两个TextField组件

实际上两个组件十分类似,我们可以采取状态提升将两个组件整成一个自定义组件,这过程则是用到了状态提升

@Composable
fun LoginPageDemo() { //这里只是演示用,实际情况MutableLiveData对象是要从ViewModel中取值的!
val mlivedate = MutableLiveData("myusername") //name为State<String>对象,只能读数值,不能修改数值
var name = mlivedate.observeAsState(initial = "") Column() {
InputText(
imageVector = Icons.Default.AccountBox,
tip = "请输入用户名",
value = name,
onValueChange = { name = it })
InputText(
imageVector = Icons.Default.Lock,
tip = "请输入密码",
value = pwd,
onValueChange = { pwd = it },isPwd = true)
}
} @Composable
fun InputText(
imageVector: ImageVector,
tip: String,
value: String,
onValueChange: (String) -> Unit,
isPwd: Boolean = false
) { val pwdVisualTransformation = PasswordVisualTransformation()
var showPwd by remember {
mutableStateOf(true)
}
val transformation = if (showPwd) pwdVisualTransformation else VisualTransformation.None TextField(
modifier = Modifier.fillMaxWidth(),
value = value,
placeholder = {
Text(tip)
},
onValueChange = onValueChange,
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
leadingIcon = {
Icon(
imageVector = imageVector,
contentDescription = null
)
},
//密码相关配置
visualTransformation = if (isPwd) transformation else VisualTransformation.None,
trailingIcon = {
if (isPwd) {
if (showPwd) {
IconButton(onClick = { showPwd = !showPwd }) {
Icon(
painter = painterResource(id = R.drawable.eye_hide),
contentDescription = null,
Modifier.size(30.dp)
)
}
} else {
IconButton(onClick = { showPwd = !showPwd }) {
Icon(
painter = painterResource(id = R.drawable.eye_show),
contentDescription = null,
Modifier.size(30.dp)
)
}
}
}
},
)
}

我们不细看代码,从组件的输入参数入手

fun InputText(
imageVector: ImageVector,
tip: String,
value: String,
onValueChange: (String) -> Unit,
isPwd: Boolean = false
) {}

其中,value对应的则是TextField的数值,onValueChange则是对应的用户输入事件,我们将此两个封装到参数上,由上层进行设置

PS: 实际上,无状态可组合项是属于比较理想的情形。具体情形中,组合函数里还是会有使用到remember函数进行状态的保存,如上文举出的代码例子

只是我们尽可能让组合项拥有尽可能少的状态,并能够在必要时通过在可组合项的 API 中公开状态来提升状态。

InputText组合函数中,还是存在有remember进行状态的保存,但此函数里的状态不会被上层改变,所以也可以将其视为无组合项(即使与上面说的概念定义有所矛盾)

总结来说:

如果组合函数中存在有value: TonValueChange: (T) -> Unit,我们可以将其视为有状态的可组合项

当然,上面是我自己个人理解,可能不太准确,大家也可以提出意见

一般来说,状态可能还涉及个单一信任源的问题,但笔者还未实践,还是等之后研究到了一起讲解

参考

Jetpack Compose学习(8)——State及remeber的更多相关文章

  1. Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用

    原文地址: Jetpack Compose学习(3)--图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝 本篇分别对常用的组件:图标(Ic ...

  2. Jetpack Compose学习(4)——Image(图片)使用及Coil图片异步加载库使用

    原文地址 Jetpack Compose学习(4)--Image(图片)使用及Coil图片异步加载库使用 | Stars-One的杂货小窝 本篇讲解下关于Image的使用及使用Coil开源库异步加载网 ...

  3. Jetpack Compose学习(6)——关于Modifier的妙用

    原文: Jetpack Compose学习(6)--关于Modifier的妙用 | Stars-One的杂货小窝 之前学习记录中也是陆陆续续地将常用的Modifier的方法穿插进去了,本期就来详细的讲 ...

  4. Jetpack Compose学习(9)——Compose中的列表控件(LazyRow和LazyColumn)

    原文:Jetpack Compose学习(9)--Compose中的列表控件(LazyRow和LazyColumn) - Stars-One的杂货小窝 经过前面的学习,大致上已掌握了compose的基 ...

  5. Jetpack Compose学习(1)——从登录页开始入门

    原文地址:Jetpack Compose学习(1)--从登录页开始入门 | Stars-One的杂货小窝 Jetpack Compose UI在前几天出了1.0正式版,之前一直还在观望,终于是出了正式 ...

  6. Jetpack Compose学习(2)——文本(Text)的使用

    原文: Jetpack Compose学习(2)--文本(Text)的使用 | Stars-One的杂货小窝 对于开发来说,文字最为基础的组件,我们先从这两个使用开始吧 本篇涉及到Kotlin和DSL ...

  7. Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用

    原文:Jetpack Compose学习(5)--从登录页美化开始学习布局组件使用 | Stars-One的杂货小窝 本篇主要讲解常用的布局,会与原生Android的布局控件进行对比说明,请确保了解A ...

  8. Jetpack Compose学习(7)——MD样式架构组件Scaffold及导航底部菜单

    Jetpack Compose学习(7)--MD样式架构组件Scaffold及导航底部菜单 | Stars-One的杂货小窝 Compose给我们提供了一个Material Design样式的首页组件 ...

  9. Jetpack Compose What and Why, 6个问题

    Jetpack Compose What and Why, 6个问题 1.这个技术出现的背景, 初衷, 要达到什么样的目标或是要解决什么样的问题. Jetpack Compose是什么? 它是一个声明 ...

随机推荐

  1. 软件开发架构,网络编程简介,OSI七层协议,TCP和UDP协议

    软件开发架构 什么是软件开发架构 1.软件架构是一个系统的草图. 2.软件架构描述的对象是直接构成系统的抽象组件. 3.各个组件之间的连接则明确和相对细致地描述组件之间的通讯. 4.在实现阶段,这些抽 ...

  2. 海量数据存储ClickHouse

    ClickHouse介绍 ClickHouse的由来和应用场景 俄罗斯Yandex在2016年开源,使用C++编写的列式存储数据库,近几年在OLAP领域大范围应用 官网:https://clickho ...

  3. 宽字符输出中文,Devc++解决方法

    有群友问类似问题,然后我编译了一下试试: #include <stdio.h> #include <wchar.h> #include <locale.h> int ...

  4. 【Java面试】Spring中 BeanFactory和FactoryBean的区别

    一个工作了六年多的粉丝,胸有成竹的去京东面试. 然后被Spring里面的一个问题卡住,唉,我和他说,6年啦,Spring都没搞明白? 那怎么去让面试官给你通过呢? 这个问题是: Spring中Bean ...

  5. 目标检测复习之Loss Functions 总结

    Loss Functions 总结 损失函数分类: 回归损失函数(Regression loss), 分类损失函数(Classification loss) Regression loss funct ...

  6. 微信小程序避坑指南——input框里的图标在部分安卓机里无法点击的问题

    问题场景: 下图中的显隐密码和验证码均为包裹在 input标签 中的 image标签, 但在开发测试中发现点击不了这俩个image标签,因为是被input标签的padding挡住了. 解决方法:将im ...

  7. 关于TornadoFx和Android的全局配置工具类封装实现及思路解析

    原文地址: 关于TornadoFx和Android的全局配置工具类封装实现及思路解析 - Stars-One的杂货小窝 目前个人开发软件存在设置页面,可以让用户自定义些设置,但我发现,存储数据的代码逻 ...

  8. 快速 IO

    IO 的进化史 cin和cout 刚开始学的时候,老师叫我们用 cin 和 cout 大概是因为这最简单吧 cin>>x; cout<<x scanf和printf 学到函数了 ...

  9. 掘地三尺搞定 Redis 与 MySQL 数据一致性问题

    Redis 拥有高性能的数据读写功能,被我们广泛用在缓存场景,一是能提高业务系统的性能,二是为数据库抵挡了高并发的流量请求,点我 -> 解密 Redis 为什么这么快的秘密. 把 Redis 作 ...

  10. 重学ES系列之新型数据结构Map应用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...