一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 2 天,点击查看活动详情。
概念
State是用来描绘界面状态的一种方式,我们肉眼所能看到的一切界面元素(包含颜色、尺寸、样式、内容等)都可以描述为界面元素的一种状态,而改变State的值则是更新界面状态的唯一方式,这是由Compose
中的组合和重组的工作原理造成的。
State 与 Composable
直接举两个例子进行说明。
TextField(输入框)
上面的输入框没有使用state,因此我们不能修改其中的内容。
下面的使用了state,将state的值作为输入框显示的内容,并在请求内容变更的回调函数中将新的内容值设置给了state。
Column {
TextField(value = "不能修改内容", onValueChange = {})
/*----------------------------------------*/
val inputState = remember { mutableStateOf("可以随意修改内容") }
TextField(
value = inputState.value,
onValueChange = { inputState.value = it }
)
}
Switch(开关)
上面的开关没有使用state,因此我们并不能修改其开关状态。
下面的开关使用了state,将state的值作为开关的是否开启的状态,并在请求开关状态变更的回调函数中将新的值设置给了state。
Column {
Switch(checked = true, onCheckedChange = {})
/*----------------------------------------*/
val switchState = remember { mutableStateOf(true) }
Switch(
checked = switchState.value,
onCheckedChange = { switchState.value = it }
)
}
在上面的两个例子中,我们为了使可组合函数能够正常执行,还用到了remember
,它的作用是帮助我们在进行重组之后保持原有的状态。正因为State在界面交互上的巨大作用,因此在实际开发中合理地设计和管理State,将会是我们编写高效易维护代码的重点之一。
有状态 与 无状态
简单来说,有状态的可组合函数中至少包含一个State数据,无状态的可组合函数中没有哪怕一个State数据。
接下来,分别使用两种方式来实现下图的效果。
- 有状态
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SwitchContent()
}
}
@Composable
fun SwitchContent() {
val textState = remember { mutableStateOf(true) }
Column {
Text(text = if (textState.value) "Opened" else "Closed")
Switch(checked = textState.value, onCheckedChange = { textState.value = it })
}
}
- 无状态
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val isOpenedState = remember { mutableStateOf(true) }
SwitchContent(
isOpened = isOpenedState.value,
onChanged = { isOpenedState.value = it }
)
}
}
@Composable
fun SwitchContent(isOpened: Boolean, onChanged: (Boolean) -> Unit) {
Column {
Text(text = if (isOpened) "Opened" else "Closed")
Switch(checked = isOpened, onCheckedChange = { onChanged.invoke(it) })
}
}
运行后,可以看到两种实现方式的呈现效果是一模一样的,两者之间的不同在于有状态的将状态的定义和更新逻辑写在了自己的内部,无状态的则是将状态的定义和更新逻辑移到了自身之外,由调用方自行去实现定义和更新逻辑。
在实际开发中,对于选择实现有状态还是无状态的可组合函数,可遵循下表:
有状态 | 无状态 | |
---|---|---|
调用方需要控制状态 | ||
调用方不关心状态的变化 |
状态的恢复与存储
恢复
在重新创建 activity 或进程后,您可以使用 rememberSaveable
恢复界面状态。rememberSaveable
可以在重组后保持状态。此外,rememberSaveable
也可以在重新创建 activity 和进程后保持状态。
存储
直接看下面来自于官方的例子:
-
能被添加到
Bundle
中的数据类型:直接使用MutableState<T>
便会默认自动保存。var name by rememberSaveable { mutableStateOf("") }
-
不能被添加到
Bundle
中的数据类型:可选择添加@Parcelize
注解。@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
-
如果因为某种原因不适合添加
@Parcelize
注解:使用mapSaver
定义自己的规则,规定如何将对象转换为系统可保存到Bundle
的一组值。data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
-
如果又想要避免像
mapSaver
那样为映射定义key:可以选择使用listSaver
,并将其索引用作key。data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }