开始:
目标是在一个text下面设置一个输入框,随着输入框变化,text也跟着变化。于是我写一个Compose function:
@Composable
fun TextAndTextField() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello",
modifier = Modifier.padding(bottom = 6.dp),
style = MaterialTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OutlinedTextField(value = "", onValueChange = {
}, label = {
Text(text = "name")
})
}
}
在框内输入任何东西都不会有反应。
接着:
因为我们text的内容是hard code的,我们可以试着使用一个变量来代替:
@Composable
fun TextAndTextField() {
var name = ""
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello,$name",
modifier = Modifier.padding(bottom = 6.dp),
style = MaterialTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OutlinedTextField(value = name, onValueChange = {
name = it
}, label = { Text(text = "name")})
}
}
但效果却和上例一样。因为可组合函数改变UI是靠重新发出(recompose)来改变的,也就是说,当state状态改变,就重新发出可组合函数的重新组合,在重新绘制UI来改变UI。但是我们的name是一个普通的变量,是不能被compose“记住的”,name改变但系统不知状态已改变, 我们需要用特殊的变量来表示一个“状态”,当这个变量改变,使得系统知道这个compose的状态改变,就重新绘制,改变UI。
remember上场:
@Composable
fun TextAndTextField() {
val name = remember { mutableStateOf("")}
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello,${name.value}",
modifier = Modifier.padding(bottom = 6.dp),
style = MaterialTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OutlinedTextField(value = name.value, onValueChange = {
name.value = it
}, label = { Text(text = "name")})
}
}
remember可以记住“{}”里面的表达式返回的值,当表达式里面的值改变时,就代表状态改变了,就通知Compose重新绘制。
note:若是旋转屏幕,状态会丢失,可以使用rememberSavable来代替remember。
state hoisting
一个具有高可重用性的compose function应该是stateless的,即不应该在内部包含状态。我们应该把状态提到外部去,来提升可重用性。
@Composable
fun HelloScreen() {
val name = remember {
mutableStateOf("")
}
TextAndTextField(name = name.value, onValueChange = {
name.value = it
})
}
@Composable
fun TextAndTextField(name: String, onValueChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello,$name",
modifier = Modifier.padding(bottom = 6.dp),
style = MaterialTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OutlinedTextField(value = name, onValueChange = onValueChange, label = { Text(text = "name") })
}
}
Compose 中的状态提升是一种将状态移至可组合项的调用方以使可组合项无状态的模式。Jetpack Compose 中的常规状态提升模式是将状态变量替换为两个参数:
value: T
:要显示的当前值onValueChange: (T) -> Unit
:请求更改值的事件,其中T
是建议的新值
不过,并不局限于 onValueChange
。如果更具体的事件适合可组合项,您应使用 lambda 定义这些事件,就像使用 onExpand
和 onCollapse
定义适合 ExpandingCard
的事件一样。
使用viewmodel和observeAsState:
首先添加依赖:
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
接着看代码:
class MainActivity : ComponentActivity() {
private val helloViewModel:HelloViewModel by viewModels()//初始化viewmodel,否则旋转屏幕无法保存状态
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloScreen(helloViewModel)
}
}
}
class HelloViewModel : ViewModel() {
private val _name = MutableLiveData<String>("")
val name: LiveData<String> get() = _name
fun onValueChanged(newName: String) {
_name.value = newName
}
}
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = HelloViewModel()) {
val name: String by helloViewModel.name.observeAsState("")
val onValueChanged:(String) -> Unit = {helloViewModel.onValueChanged(it)}
TextAndTextField(name = name, onValueChange = onValueChanged)
}
@Composable
fun TextAndTextField(name: String, onValueChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello,$name",
modifier = Modifier.padding(bottom = 6.dp),
style = MaterialTheme.typography.h5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OutlinedTextField(
value = name,
onValueChange = onValueChange,
label = { Text(text = "name") })
}
}
e:使用by viewModels的语法需要activity-ktx的依赖。