原文地址: 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. 【Azure Redis 缓存】 Python连接Azure Redis, 使用redis.ConnectionPool 出现 "ConnectionResetError: [Errno 104] Connection reset by peer"

    问题描述 Python连接Azure Redis, 使用redis.ConnectionPool 出现 "ConnectionResetError: [Errno 104] Connecti ...

  2. OAuth2密码模式已死,最先进的Spring Cloud认证授权方案在这里

    旧的Spring Security OAuth2停止维护已经有一段时间了,99%的Spring Cloud微服务项目还在使用这些旧的体系,严重青黄不接.很多同学都在寻找新的解决方案,甚至还有念念不忘密 ...

  3. spring boot validation

    先简单打个草稿 @NotNull 不能为 null @NotEmpty 不能为空(允许空格) ,只能用于字符串 @NotBlank 不能为空(trim()后) 用于验证字符串不为空且不能全为空格,只能 ...

  4. SQLServer2008中的Merge

    SqlServer2008 +  中的 Merge Merge:  合并   融合 SqlServer2008 中的Merge 用于匹配两种表中的数据,根据源表和目标表中的数据的比较结果对目标表进行对 ...

  5. Java_选择结构

    if单选择结构 if(布拉尔表达式){ //如果布拉尔表达式为true将执行的语句 } if双选择结构 if(布拉尔表达式){ //如果布拉尔表达式的值为true }else{ //如果布拉尔表达式的 ...

  6. java继承中关于super关键字和this关键字的使用

    1.super关键字 由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super 关键字.super 可以用来访问父类的构造方法.普通方法和属性.super 关键字的功能: ...

  7. 一文带你搞懂 SSR

    欲语还休,欲语还休,却道天凉好个秋 ---- <丑奴儿·书博山道中壁>辛弃疾 什么是 SSR ShadowsocksR?阴阳师?FGO? Server-side rendering (SS ...

  8. php 使用phpqrcode生成二维码并上传到OSS

    一般情况调用phpqrcode第三方插件 会把生成的二维码图片保存到服务器,不保存服务器也会以header头的形式输出到浏览器,(我们不允许把图片文件保存的liunx服务器,只能保存到阿里云OSS存储 ...

  9. 19.Tomcat多实例部署及负载均衡、动静分离

    Tomcat多实例部署及负载均衡.动静分离 目录 Tomcat多实例部署及负载均衡.动静分离 Tomcat多实例部署 安装jdk 设置jdk环境变量 安装tomcat 配置 tomcat 环境变量 修 ...

  10. BUUCTF-[BJDCTF2020]认真你就输了

    [BJDCTF2020]认真你就输了 下载通过16进制查看发现是压缩包,直接就binwalk分离查看. 分离直接得到几个文件,不过好像压缩包里的和外面的文件是一样的,所以直接翻一下目录 直接就找到了 ...