提示:需要对基本的compose语法有所了解`
阅读本文需要一定compose基础,如果没有请移步Jetpack Compose入门详解(实时更新)
本文介绍Compose中状态与事件的基本概念以及规范。根据官网教程总结,如有不对请在评论区指教
应用的“状态”是指可以随时间变化的任何值。这是一个非常宽泛的定义,从 Room 数据库到类的变量,全部涵盖在内。
所有 Android 应用都会向用户显示状态。下面是 Android 应用中的一些状态示例:
下面我们通过一个例子来对状态有一个深刻的印象(虽然钱包余额已经够深刻了),对于此时此刻的我来说会使我印象深刻的就是国庆节的余额
@Composable
fun NationalDay(modifier: Modifier = Modifier) {
val day = 0
Text(
text = "我们有 $day 天来庆祝国庆节!",
modifier = modifier.padding(16.dp)
)
}
然后我们将其作为可组合项在setContent中调用起来得到如下界面
对!今天是最后一天啦,没有时间庆祝了!
咳咳,话不扯远了,在本例中,变量 val day = 0 就是我们的一个状态,可静态状态‘val’无法修改,因此用处不大。
“状态”是指可以随时间变化的任何值,例如,聊天应用最新收到的消息。但是,是什么原因导致状态更新呢?在 Android 应用中,状态会根据事件进行更新。
事件是从应用外部或内部生成的输入,例如:
应用的状态说明了要在界面中显示的内容,而事件则是一种机制,可在状态发生变化时导致界面发生变化。
这里用官方图可能更清晰一点
那么正常以按下按钮为例来说,我们的例子应该变成这样
添加一个按钮,将变量day改为var类型,然后在按钮按下是将day+1
@Composable
fun NationalDay(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var day = 0
Text(
text = "我们有 $day 天来庆祝国庆节!",
modifier = modifier.padding(16.dp)
)
Button(onClick = { day++ }) {
Text(text = "加一天")
}
}
}
但实际上这并不会有任何效果,这是因为compose在按钮按下前已经绘制完成,当day变量发生变化时,compose并没有重组,它并不知道day已经从0变为1了,这时,我们需要使用到remember函数来帮助compose记住day变量是否发生变化。
这个内嵌函数中文释义如下
remember 可组合内嵌函数。系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间一直保持存储的值。
示例如下:
import androidx.compose.runtime.remember
@Composable
fun NationalDayVal(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
val day: MutableState<Int> = remember { mutableStateOf(0) }
Text(
text = "我们有 ${day.value} 天来庆祝国庆节!",
modifier = modifier.padding(16.dp)
)
Button(onClick = { day.value++ }) {
Text(text = "加一天")
}
}
}
此时我们可以发现代码生效了
我们也可以通过kotlin简化上例写法,效果是一样的
@Composable
fun NationalDayVar(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var day by remember { mutableStateOf(0) }
Text(
text = "我们有 $day 天来庆祝国庆节!",
modifier = modifier.padding(16.dp)
)
Button(onClick = { day++ }) {
Text(text = "加一天")
}
}
}
注意: remember 会将对象存储在组合中,而如果在重组期间未再次调用之前调用 remember 的来源位置,则会忘记对象。
为了更直观的展示我们新增一个提示UI如下
@Composable
fun NationalDayNoticeItem(
noticeName: String,
onClose: () -> Unit,
modifier: Modifier = Modifier
){
Row(
modifier = modifier, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
text = noticeName
)
IconButton(onClick = onClose) {
Icon(Icons.Filled.Close, contentDescription = "Close")
}
}
}
国庆七天假期,当到最后一天时给用户一个提示,要上班了,将NationalDayNoticeItem添加到NationalDayVar中,修改NationalDayVar如下
@Composable
fun NationalDayVar(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var day by remember { mutableStateOf(7) }
if (day<1){
var showNotice by remember { mutableStateOf(true) }
if (showNotice){
NationalDayNoticeItem(
onClose = {showNotice = false },
noticeName = "你要上班啦"
)
}
}
Text(
text = "我们有 $day 天来庆祝国庆节!",
modifier = modifier.padding(16.dp)
)
Row(Modifier.padding(top = 8.dp)) {
Button(onClick = { day-- }) {
Text(text = "玩一天")
}
Button(onClick = { day = 7 }, Modifier.padding(start = 8.dp)) {
Text("重新过国庆")
}
}
}
}
我们新增了一个showNotice 的状态当国庆假期只剩一天时,改变状态并弹出提示,为了方便添加了一个重新过国庆的按钮(多希望真的能重新过),效果如下
初始状态是这样的
当我们点击玩一天时,day状态会变化,而当day的值为0时showNotice 状态会初始化为true并展示notice
这时我们点击X关掉提示,再调休一天,并不会出现提示,这是因为我们记住了showNotice 的值为在点击X时已经变更为false
接下来是重头戏,我们点击重新过国庆,这时我们时光倒流,又有七天假期可以庆祝国庆了
在我们玩到最后一天时,提示又出现告诉我们要上班了
(是不是有一种进入循环或者夏日重现的感觉)按理说showNotice 在我们第一次点击X时已经被置为false并且已经生效,第二次还是会提示我们要上班了,这是因为在day状态变为7时,会有第二次进入day<1这个过程,这时状态showNotice会被重置为true并展示,所以我们可以修改代码如下
@Composable
fun NationalDayVar(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var day by remember { mutableStateOf(7) }
var showNotice by remember { mutableStateOf(false) }
var count by remember {mutableStateOf(0)}
if (day<1){
if (count==0){
showNotice = true
}
count++
if (showNotice){
NationalDayNoticeItem(
onClose = {showNotice = false },
noticeName = "你要上班啦"
)
}
}
Text(
text = "我们有 $day 天来庆祝国庆节!",
modifier = modifier.padding(16.dp)
)
Row(Modifier.padding(top = 8.dp)) {
Button(onClick = { day-- }) {
Text(text = "玩一天")
}
Button(onClick = { day = 7 }, Modifier.padding(start = 8.dp)) {
Text("重新过国庆")
}
}
}
}
我们将showNotice状态提前到使用它之前,这时我们再次重新过国庆,就不会弹出提示,谷歌官方将showNotice这个状态的初始化提升到使用它之前这个行为称之为状态提升,这利于我们对状态的管理
正当我玩这个过国庆玩的不亦乐乎时,我不小心手滑了一下,我的手机差点掉到了地上,还好我眼疾手快抓住了它,但是拿起来一看,我的屏幕发生了旋转,本来只有0天过国庆的屏幕,重新变成了七天,我对天发誓我没有按到重新过国庆的按钮。并且我再一次玩到0天时,它又出现了上班提醒,我真的头都大了,我不想看到上班两个字,查阅资料后,发现这是因为在屏幕旋转后我的配置发生了变化,这导致compose退出组合并重组,忘记了所有已经更改的状态。要改变这一情况,就要说到到rememberSaveable 了。
我们将代码中的 remember更改为rememberSaveable
@Composable
fun NationalDayVar(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var day by rememberSaveable { mutableStateOf(7) }
var showNotice by rememberSaveable { mutableStateOf(false) }
var count by rememberSaveable {mutableStateOf(0)}
...
}
}
这样旋转屏幕也不会让我第二次看到上班提醒啦!
在点击事件的onClick中,需要通过状态去改变函数,而不是直接调用compose函数将可组合项添加进去,参考所有上例;这是因为compose架构旨在用状态管理事件
虽然rememberSaveable可以保存状态更改后的值但这并不是说remember 没有rememberSaveable好,请根据应用的状态和用户体验需求来考虑是使用 remember 还是 rememberSaveable。