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是什么? 它是一个声明 ...
随机推荐
- 【多线程】可重入锁 ReentrantLock
java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能.而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也 ...
- Python数据分析--Numpy常用函数介绍(2)
摘要:本篇我们将以分析历史股价为例,介绍怎样从文件中载入数据,以及怎样使用NumPy的基本数学和统计分析函数.学习读写文件的方法,并尝试函数式编程和NumPy线性代数运算,来学习NumPy的常用函数. ...
- SpringMVC请求流程源码分析
一.SpringMVC使用 1.工程创建 创建maven工程. 添加java.resources目录. 引入Spring-webmvc 依赖. <dependency> <group ...
- DataX异构数据源离线同步工具json文件配置说明
DataX 是阿里开源的一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDFS.Hive.ODPS.HBase.FTP等各种异构数据源之间稳定高效的数据同步功能 ...
- 探究Presto SQL引擎(3)-代码生成
vivo 互联网服务器团队- Shuai Guangying 探究Presto SQL引擎 系列:第1篇<探究Presto SQL引擎(1)-巧用Antlr>介绍了Antlr的基本用法 ...
- 【Github】 Github访问不是私密连接问题
前言 GitHub是一个软件项目的托管平台,是我们经常需要访问的,我原本在学校时候虽然网速比较慢,但是还以能够满足一些代码下载和上传的,在暑假回到家,再去访问的时候就出现了不能访问的问题. 问题描述 ...
- 搭建个人博客,Docsify+Github webhook+JGit解决方案
一开始博客使用的 Halo,发现问题比较多啊,时不时的莫名其妙主题各种报错,有时候还要升级,麻烦的要死,于是就想弄简单点. 这两天抽空反复倒腾了一遍,不小心还把镜像给尼玛删了,发的文章都没了,痛定思痛 ...
- python中 OS模块中 os.path.join() 函数用法简介
基础用法 os.path.join() 用于拼接文件的路径,可以传入多个待拼接的路径 若各个路径之间不存在 " / ", 则其会自动为各个路径之间增加连接符 " / &q ...
- JS:typeof
想要弄明白某一个变量中保存的数据到底是什么数据类型,我们可以使用到typeof操作符. typeof操作符:检测变量的数据类型. 看例子! var a = "abc"; var b ...
- 强化学习-linux安装gym、atari和box2d环境
安装gym和atari环境 pip3 install gym pip3 install gym[atari] pip3 install gym[accept-rom-license] 安装box2d环 ...