Compose TextField 还可以这么写

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的一些特性,属性委托,解构声明,内联方法等等可以帮助我们写出精简又健壮的代码。前提是需要对它内部的实现有了解。

相关链接

相关推荐
ZhengEnCi33 分钟前
12 种高级 UI 交互效果及对应 AI 提示词:从按压回弹到底部抽屉的物理感教学
前端·css·交互设计
IT_陈寒3 小时前
Redis大key删除为什么会导致服务卡顿?我的线上事故复盘
前端·人工智能·后端
用户2705077725244 小时前
MCP 最小 Server 实测:3 个 JSON-RPC 报错与修法
前端
用户499335025074 小时前
一人VIP账号多人共用:Web 代理里的会话共享怎么实现
前端
用户老李4 小时前
无线电自学教程 05:第一台收音机——从矿石机到 SDR
前端
IPIPD4 小时前
Node.js请求遇到503怎么重试?先解析Retry-After,再做有上限退避
前端
葡萄城技术团队4 小时前
dsh-spreadjs-editor 的集成链路:把 SpreadJS Designer 嵌进 Agent 工作台
前端
FlyWIHTSKY5 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js
码云之上5 小时前
10 颗一模一样的「Edit」:中后台 Web Agent 怎么找到对的那一行
前端·人工智能·浏览器
量化分析码农6 小时前
【Python量化多因子实战 04】因子暴露不是 alpha:市值与行业中性化实战
前端·后端