Jetpack Compose学习(8)——State及remeber
原文地址: Jetpack Compose学习(8)——State状态及remeber关键字 - Stars-One的杂货小窝
之前我们使用TextField,使用到了两个关键字remember
和mutableStateOf
,这两个是做什么用的呢?本篇特来补充说明下
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称为数值类型
此方法接收两个参数,value
和policy
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库中主要是两个类MutableLiveData
和LiveData
,使用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("")
}
状态提升
无状态可组合项: 没有状态的可组合项,这意味着它不会保存、定义或修改新状态。
有状态可组合项: 具有可以随时间变化的状态的可组合项。
简单来说,组合中使用 remember
、rememberSaveState
方法保存状态的组合项是有状态组合,没有则是无状态组合。
一般如果涉及到组合的复用,则需要我们将组合由有状态组合提取为无状态组合,这就叫做状态提升
当一个组合函数,引入了下面两个参数,即可说此组合其属于有状态组合
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: T
和onValueChange: (T) -> Unit
,我们可以将其视为有状态的可组合项
当然,上面是我自己个人理解,可能不太准确,大家也可以提出意见
一般来说,状态可能还涉及个单一信任源的问题,但笔者还未实践,还是等之后研究到了一起讲解
参考
- State | Android Developers
- remember | Android Developers
- mutableStateOf | Android Developers
- collectAsState | Android Developers
- 状态和Jetpack Compose
- Compose的State(九)
- Jetpack Compose学习之mutableStateOf与remember是什么
- Jetpack Compose状态管理
- Compose中的remember和mutableStateOf
- JetpackCompose之状态管理 - 掘金
- Kotlin Compose 状态恢复 rememberSaveable 与 remember_安果移不动的博客-CSDN博客
- Compose 中的状态简介
Jetpack Compose学习(8)——State及remeber的更多相关文章
- Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用
原文地址: Jetpack Compose学习(3)--图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝 本篇分别对常用的组件:图标(Ic ...
- Jetpack Compose学习(4)——Image(图片)使用及Coil图片异步加载库使用
原文地址 Jetpack Compose学习(4)--Image(图片)使用及Coil图片异步加载库使用 | Stars-One的杂货小窝 本篇讲解下关于Image的使用及使用Coil开源库异步加载网 ...
- Jetpack Compose学习(6)——关于Modifier的妙用
原文: Jetpack Compose学习(6)--关于Modifier的妙用 | Stars-One的杂货小窝 之前学习记录中也是陆陆续续地将常用的Modifier的方法穿插进去了,本期就来详细的讲 ...
- Jetpack Compose学习(9)——Compose中的列表控件(LazyRow和LazyColumn)
原文:Jetpack Compose学习(9)--Compose中的列表控件(LazyRow和LazyColumn) - Stars-One的杂货小窝 经过前面的学习,大致上已掌握了compose的基 ...
- Jetpack Compose学习(1)——从登录页开始入门
原文地址:Jetpack Compose学习(1)--从登录页开始入门 | Stars-One的杂货小窝 Jetpack Compose UI在前几天出了1.0正式版,之前一直还在观望,终于是出了正式 ...
- Jetpack Compose学习(2)——文本(Text)的使用
原文: Jetpack Compose学习(2)--文本(Text)的使用 | Stars-One的杂货小窝 对于开发来说,文字最为基础的组件,我们先从这两个使用开始吧 本篇涉及到Kotlin和DSL ...
- Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用
原文:Jetpack Compose学习(5)--从登录页美化开始学习布局组件使用 | Stars-One的杂货小窝 本篇主要讲解常用的布局,会与原生Android的布局控件进行对比说明,请确保了解A ...
- Jetpack Compose学习(7)——MD样式架构组件Scaffold及导航底部菜单
Jetpack Compose学习(7)--MD样式架构组件Scaffold及导航底部菜单 | Stars-One的杂货小窝 Compose给我们提供了一个Material Design样式的首页组件 ...
- Jetpack Compose What and Why, 6个问题
Jetpack Compose What and Why, 6个问题 1.这个技术出现的背景, 初衷, 要达到什么样的目标或是要解决什么样的问题. Jetpack Compose是什么? 它是一个声明 ...
随机推荐
- Centos7最小化安装报错There are no enabled repos. Run "yum repolist all" to see the repos you have.解决办法
原因是缺少CentOS-Base.repo文件,因为我这台机器wget也不能用,所以我是下载到本地sftp上去的,传输的时候一定要在root用户下,否则会无法启动传输 这是报错的完整信息:Loadin ...
- sa-token 配置 CORS
return new SaServletFilter() ... .setBeforeAuth(r -> { // 前置函数,在认证函数每次执行前执行 // 设置一些安全响应头之类的玩意 SaH ...
- 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- ESP32+阿里云+vscode_Pio
用ESP32在vscode使用PlatformPIO写的代码.(代码是折叠代码,不能一眼瞧见,我也不太会使用编辑器哈,刚写博不久,望谅解.) 功能:esp32联网,能够通过联网打开在阿里云平台控制设备 ...
- 亿信BI——EXCEL组件使用流程
功能模块: 用户点击"excel输入"模块进行excel文件导入操作,excel导入页面的顶部导航栏包括基本属性.文件设置.格式设置和字段列表四部分. 基础属性: 在基本属性模块部 ...
- 一文讲透为Power Automate for Desktop (PAD) 实现自定义模块 - 附完整代码
概述 Power Automate for Desktop (以下简称PAD)是微软推出的一款针对Windows桌面端的免费RPA(机器人流程自动化)工具,它目前默认会随着Windows 11安装,但 ...
- Stream.toList()和Collectors.toList()的性能比较
昨天给大家介绍了Java 16中的Stream增强,可以直接通过toList()来转换成List. 主要涉及下面这几种转换方式: list.stream().toList(); list.stream ...
- C# 与LINQ有关的语言特性
1.隐式类型 我们知道强类型语言 C C++ C# Java 对变量的定义前必须要确定这个变量是什么类型的 例如 string str="abc"; int num ...
- JavaSE_多线程入门 线程安全 死锁 状态 通讯 线程池
1 多线程入门 1.1 多线程相关的概念 并发与并行 并行:在同一时刻,有多个任务在多个CPU上同时执行. 并发:在同一时刻,有多个任务在单个CPU上交替执行. 进程与线程 进程:就是操作系统中正在运 ...
- JAVA - ArrayList是否会越界?
JAVA - ArrayList是否会越界? ArrayList并发add()可能出现数组下标越界异常. ArrayList是实现了基于动态数组的数据结构. LinkedList是基于链表的数据结构 ...