持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情
概述
应用中的状态是指可以随时间变化的任何值,所有Android应用都会向用户展示状态,比如在没有网络连接的时候显示无网络的控件,点击按钮时的水波纹效果等。状态是一个很宽泛的定义,从数据库到变量的定义全部涵盖在内。Compose提供了相关的API,可以帮助我们处理状态。
初识状态
和使用xml进行UI编程的框架不同,Compose中的可组合项会在状态变化之后自行刷新屏幕,从而显示新的组合。因此,TextField不会像之前的EditText那样自动更新。可组合项必须明确获得新的状态才能进行相应的更新。
下面的代码定义了一个输入框,但是由于我们记录任何状态,导致无法向输入框中输入任何数据:
TextField(
value = "",
onValueChange = { },
label = { Text("Name") },
modifier = Modifier.fillMaxWidth()
)
运行上面的代码,可以看到如下的效果:
从图片上可以看到,无论我们怎么点击键盘上的按键,均不会有任何字符显示在输入框中.
使用remember记录状态
我们可以使用remember可组合项记住单个对象,系统会在初始组合期间将remember计算的值存储在组合中,并在重组期间返回存储的值。remember既可以用于存储可变对象,也可以用于存储不可变对象。
下面的代码演示了使用remember去记住一个字符串,并且在文本输入框的内容改变之后,使用这个变量去保存最新的输入框中的内容:
Text(text = "使用remember记住某个值")
var name1 = remember {
"123213"
}
Text(text = "Hello $name1")
TextField(value = name1, onValueChange = {
name1 = it
Log.i(TAG, "it:$it, name1 is: $name1")
}, label = {
Text(text = "Name")
})
运行上面的程序,我们可以看到如下的效果:
从上面的动图中可以看到,输入框中仍然没有显示出来我们输入的数据,但是我们还会在控制台看到下面的输出信息:
I/ManageStateFragment: name1 is: 123213
I/ManageStateFragment: it:123213d, name1 is: 123213d
I/ManageStateFragment: name1 is: 123213d
I/ManageStateFragment: it:123213h, name1 is: 123213h
I/ManageStateFragment: name1 is: 123213h
I/ManageStateFragment: it:123213v, name1 is: 123213v
I/ManageStateFragment: name1 is: 123213v
I/ManageStateFragment: it:123213t, name1 is: 123213t
I/ManageStateFragment: name1 is: 123213t
I/ManageStateFragment: it:123213f, name1 is: 123213f
I/ManageStateFragment: name1 is: 123213f
I/ManageStateFragment: it:123213f, name1 is: 123213f
从上面输出的信息中可以看出:虽然我们在键盘上的按键没有显示到输入框中,但是每次按下按键的时候我们都会将输入框中最新的数据保存到name1中,通过多次打印可以看出,name1是可以保存上次输入的数据的。
之所以会出现上面的问题,是因为name1本身只是一个普通的变量,它自身提供的状态无法被Compose观察,也就是说,它的数据变化之后不会导致组合进行重组,既然无法执行重组,那么同样的无法将最新的数据设置到输入框中。
为了解决这个问题,我们可以使用State,这是一个可被观察的变量,我们可以将数据保存在这里,然后Composable可以观察其中数据的变化从而导致重组,如下代码所示:
Text(text = "使用remember和State存储状态")
val name = remember {
mutableStateOf("12321")
}
TextField(value = name.value, onValueChange = {
name.value = it
}, label = {
Text(text = "Name")
}, modifier = Modifier.fillMaxWidth())
运行上面的程序,可以看到下面的效果:
可以看到这次我们确实能够向输入框中输入数据了。
上面我们针对一个String类型的变量进行了观察,并在观察到数据变化之后进行重组,但是实际开发中我们很多时候操作的都是Bean对象,那么针对Bean对象是否可以使用这种观察呢。
在下面的代码中演示了修改对象中的数据是否能够引起组合重组:
val bean = remember {
mutableStateOf(TestBean("123",10))
}
Text(text = "I am ${bean.value.name} age:${bean.value.age}")
Button(onClick = {
bean.value.name = "312"
bean.value.age = 20
}) {
Text(text = "修改Bean数据")
}
在上面的代码中,我们通过点击按钮修改了对象中的数据,运行上面的代码可以看到如下的效果:
可以看到,直接修改对象的值也是无法引起组合进行重组的。因为我们观察的是这个对象本身,并不是对象中的值,因此,如果要在对象中的值变化之后引起组合的重组,最好是重新创建一个新的对象保存新的值。
上面的代码演示了Compose中针对对象的观察,和对象一样,我们在使用列表的时候使用ArrayList<T>或者mutableListOf<T>的时候也可能会导致数据变化之后无法进行重组,如下的代码所示:
val list = remember {
mutableStateOf<MutableList<String>>(mutableListOf("1","2","3"))
}
Text(text = "观察列表中的数据:${list.value.size}")
Button(onClick = {
list.value.add("4")
Log.i(TAG,"list size is :${list.value.size}")
}) {
Text(text = "向列表中添加数据")
}
在上面的代码中,我们在State中设置了一个可写的列表,并在按钮点击后向其中添加一些数据,但是当我们运行上面的代码的时候,会发现重组是没有发生的,然而通过日志我们会发现这里的列表中的数据确实已经被添加进去了:
因此我们应该通过State<T>和listof<T>来指定需要观察的列表,这样才能正确看到组合进行重组。
rememberSaveable
使用remember能够帮助我们记录可组合项中的状态,但是仍然有一个问题是,当我们页面的配置发生更改的时候,remember仍然会丢失其中保存的数据,如下面的图片所示:
从上面的动图中可以看出,当我们的页面旋转之后,之前设置在输入框中的数据都丢失了,从而变成了一开始设置的数据。为了解决这个问题,我们可以使用rememberSaveable来保存状态,这样状态就不会在页面配置修改的时候丢失。
下面的代码中演示了使用rememberSateable保存状态:
val name2 = rememberSaveable{
mutableStateOf("1")
}
Text(text = "使用rememberSaveable保存状态")
Text(text = "Hello ${name2.value}")
TextField(value = name2.value, onValueChange = {
name2.value = it
},label = {
Text(text = "Name")
})
在上面的代码中,我们通过rememberSaveable保存了输入框的内容,运行上面的程序可以看到下面的效果:
可以看到,在页面旋转之后我们的数据仍然存在。