Compose TextField 还可以这么写
由于Compose状态绑定的设计,所以View和State是相互独立的,在State更新是会自动重组Composable方法。
基本用法
TextField的入参提供了 value 和 onValueChange 两个基本数据参数:
kotlin
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@Composable
fun TextField1() {
var input by remember { mutableStateOf("Hello World") }
TextField(
value = input,
onValueChange = { input = it }
)
}
Compose 利用了 Kotlin by 关键字来实现对 androidx.compose.runtime.MutableState
的 value 进行访问和修改,可以查看委托相关的两个扩展方法:
kotlin
/**
* Permits property delegation of `val`s using `by` for [State].
*/
inline operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
/**
* Permits property delegation of `var`s using `by` for [MutableState].
*/
inline operator fun <T> MutableState<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}
这样我们的State就和TextField完成了双向绑定:
在用户输入时通过onValueChange回调调用state.setValue更新状态,然后触发TextField重组,重组时通过state.getValue方法获取当前输入文字显示在界面上。
利用属性解构优化写法
除了这种基本写法之外,我们是不是还有其他写法呢,我们点进 MutableState
的声明看一下:
kotlin
@Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
可以看到 MutableState
为我们实现了两个属性解构 方法 componentN()
,这两个方法是干什么的呢?
我们看一下对应的实现类 androidx.compose.runtime.SnapshotMutableStateImpl
:
kotlin
internal open class SnapshotMutableStateImpl<T> /*...*/ {
/**
* The componentN() operators allow state objects to be used with the property destructuring
* syntax
*
* ```
* var (foo, setFoo) = remember { mutableStateOf(0) }
* setFoo(123) // set
* foo == 123 // get
* ```
*/
override operator fun component1(): T = value
override operator fun component2(): (T) -> Unit = { value = it }
}
可以看出文档已经帮我们指明了这两个解构方法的功能了,我们可以利用一下它来处理这种情况
kotlin
@Composable
fun TextField2() {
var (input, onValueChange) = remember { mutableStateOf("Hello World") }
TextField(
value = input,
onValueChange = onValueChange
)
}
当然这种写法有一些注意点,通常我们的业务不可能这么简单,比如加个清空输入框的按钮
kotlin
@Composable
fun TextField3() {
var (input, onValueChange) = remember { mutableStateOf("Hello World") }
Box {
TextField(
value = input,
onValueChange = onValueChange,
)
IconButton(
modifier = Modifier.align(Alignment.CenterEnd),
onClick = {
input = ""
}
) {
Icon(imageVector = Icons.Default.Clear, contentDescription = null)
}
}
}
我们点击IconButton会是什么效果呢,大家可能已经猜到了,除了Button点击水波纹以外什么也不会改变。我们可以展开属性解构来写:
kotlin
@Composable
fun TextField3Unfold() {
val state = remember { mutableStateOf("Hello World") }
var input = state.component1()
val onValueChange = state.component2()
Box {
TextField(
value = input,
onValueChange = onValueChange,
)
IconButton(
modifier = Modifier.align(Alignment.CenterEnd),
onClick = {
input = ""
}
) {
Icon(imageVector = Icons.Default.Clear, contentDescription = null)
}
}
}
我们只是给局部变量input进行了更新,并没有更新State,也就不会触发TextField的重组,所以清空是没有效果的。
需要改为调用 MutableState setValue 更新状态,触发TextField重组,进而刷新界面:
kotlin
@Composable
fun TextField3() {
val (input, onValueChange) = remember { mutableStateOf("Hello World") }
Box {
TextField(
value = input,
onValueChange = onValueChange,
)
IconButton(
modifier = Modifier.align(Alignment.CenterEnd),
onClick = {
onValueChange.invoke("")
// 这时各属性内容:
// state.value -> ""
// input -> "Hello World"
}
) {
Icon(imageVector = Icons.Default.Clear, contentDescription = null)
}
}
}
总结
TextField 的内容绑定只是一个例子,善用Kotlin的一些特性,属性委托,解构声明,内联方法等等可以帮助我们写出精简又健壮的代码。前提是需要对它内部的实现有了解。
相关链接