原文地址: 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. GO 语言入门(一)

    GO 语言入门(一) 本文写于 2020 年 1 月 18 日 Go 由 Google 工程师 Robert Griesemer,Rob Pike 和 Ken Thompson 设计的一门编程语言,第 ...

  2. Git 后续——分支与协作

    Git 后续--分支与协作 本文写于 2020 年 9 月 1 日 之前一篇文章写了 Git 的基础用法,但那其实只是「单机模式」,Git 之所以在今天被如此广泛的运用,是脱不开分支系统这一概念的. ...

  3. Windows 程序安装与更新方案: Clowd.Squirrel

    我的Notion Clowd.Squirrel Squirrel.Windows 是一组工具和适用于.Net的库,用于管理 Desktop Windows 应用程序的安装和更新. Squirrel.W ...

  4. Redis 全局通用命令整理

    转载请注明出处: 1.查看所有键 keys * 该命令会存在线程阻塞问题,keys 命令也可以通过正则匹配获取存在的缓存数据 2.查看键总数 dbsize dbsize命令会返回当前数据库中键的总数. ...

  5. hexo + typora 图片插入解决办法

    Typora 是一款知名的 Markdown 编辑器,简单好用,体验良好.使用 hexo 搭建好博客后,主要是用 Markdown 来编写博客,typora 便是我的首选编辑器.但直接使用 typor ...

  6. js 动画补间 Tween

    1 /* RunningList (触发过程中可以安全的删除自己) 2 如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), ...

  7. VSCode进一步深入了解学习

    紧接上一章节趁热打铁吧,未关注博主的记得关注哦! VSCode设置 (1)关闭预览模式 我们在 VScode 上打开一个新文件的话会覆盖掉以前的文件,这是因为 VSCode 默认开启了预览模式,预览模 ...

  8. 回流&重绘

    浏览器加载解析页面:把HTML解析为DOM树,解析CSS生成CSSOM树,HTML DOM树和CSSOM树组合构建render树,首次触发回流和重绘后将页面结构信息发送给GPU进行绘制渲染. 回流:当 ...

  9. CSS(九):background(背景属性)

    通过CSS背景属性,可以给页面元素添加背景样式. 背景属性可以设置背景颜色.背景图片.背景平铺.背景图像固定等. background-color(背景颜色) background-color属性定义 ...

  10. Docker容器编译安装Nginx

    Docker容器编译安装Nginx,最简单的Nginx配置. 创建容器&进入容器 宿主机2080映射容器的80端口 [root@localhost ~]# docker run -i -d - ...