TextField 与基本输入:单行输入、占位符、文本变化监听、键盘选项配置
原理概览:声明式文本输入的核心机制
TextField 在 Jetpack Compose 中是一个“受控组件”,它的状态由调用方持有并驱动。与传统的编辑框不同,Compose 的 TextField 不维护自身内部可变状态,而是通过 value: String 参数接收当前文本,并通过 onValueChange: (String) -> Unit 回调将每次用户操作后的新值通知给调用方。这种单向数据流设计保证了 UI 的确定性——你总是可以看到文本的当前值是如何从上一帧的状态演变而来的。
value 和 onValueChange 必须配对使用。如果只提供 value 而不处理 onValueChange,TextField 将变成只读;如果只处理 onValueChange 而不更新 value,用户输入将无法反映到界面上。
以下是 TextField 内部状态更新流程的 SVG 图,展示了用户输入如何通过回调改变组合树中的数据源:
组件分类与 API 选择
Compose 提供了两种文本输入组件,适用场景不同:
| 组件 | 适用场景 | 关键区别 |
|---|---|---|
TextField |
Material Design 风格输入框,带下划线或填充容器 | 默认有边框/填充,可配置视觉样式 |
BasicTextField |
完全自定义外观,不带任何 Material 主题依赖 | 无默认装饰,所有样式需手动设置 |
本章聚焦 TextField,因为它内置了占位符、错误提示、键盘类型等实用参数,适合大部分业务场景。
核心参数与典型用法
1. 基本单行输入 + 占位符
占位符通过 placeholder 参数设置,当输入框为空时显示。注意这是 @Composable 参数,可以渲染任意组件:
@Composable
fun SimpleInput() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
placeholder = { Text("请输入用户名") },
modifier = Modifier.fillMaxWidth()
)
}
remember 的 mutableStateOf 没有被 by 委托,你需要在赋值时手动调用 .value,例如 text.value = it。使用 by 委托可避免这类语法噪音。
2. 文本变化监听与过滤
在 onValueChange 中可以对输入进行实时拦截和转换,实现诸如只允许数字输入、限定长度等效果:
@Composable
fun FilteredInput() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { newValue ->
// 只保留数字
val filtered = newValue.filter { it.isDigit() }
// 限定长度 6
if (filtered.length <= 6) {
text = filtered
}
},
placeholder = { Text("6位验证码") },
modifier = Modifier.fillMaxWidth()
)
}
这种“在回调中加工”的模式非常灵活,但需要注意:
- 不要在
onValueChange中直接修改newValue(它是不可变的),必须构造新字符串。 - 过滤逻辑要足够快,避免阻塞 UI 线程——复杂的正则建议用
remember缓存。
3. 键盘选项配置
通过 keyboardOptions 参数可以控制软键盘的类型、行为,以及操作按钮的标签。以下表格列出了常用选项:
| KeyboardType | 说明 |
|---|---|
KeyboardType.Text | 默认文本键盘 |
KeyboardType.Ascii | ASCII 字符键盘(适合英文输入) |
KeyboardType.Decimal | 带小数点的数字键盘 |
KeyboardType.Email | 包含 @ 和 . 的键盘 |
KeyboardType.Number | 纯数字键盘 |
KeyboardType.Password | 密码模式(隐藏输入) |
KeyboardType.Uri | URL 输入优化键盘 |
再加上 ImeAction 配置操作按钮(如“完成”、“搜索”、“发送”),可以实现更自然的键盘交互:
@Composable
fun KeyboardConfigDemo() {
var phone by remember { mutableStateOf("") }
OutlinedTextField(
value = phone,
onValueChange = { phone = it.filter { it.isDigit() } },
label = { Text("手机号") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done // 键盘右下角显示"完成"
),
keyboardActions = KeyboardActions(
onDone = {
// 收起键盘,执行提交逻辑
// 例如:focusManager.clearFocus()
println("提交手机号: $phone")
}
),
singleLine = true
)
}
onValueChange 中使用 snapshotFlow 或 debounce 来降低重组频率。但本章基础环境不需要引入额外库,只需理解回调时机即可。
4. 单行输入与多行控制
默认情况下 TextField 会根据内容自动换行。通过设置 singleLine = true 可强制单行:
TextField(
value = text,
onValueChange = { text = it },
singleLine = true, // 强制单行,输入超过宽度时水平滚动
maxLines = 1 // 与 singleLine 等效,但 singleLine 更语义化
)
常见错误与调试
- 错误:输入无反应 —— 检查
value是否确实通过onValueChange更新了。最常发生在忘记写mutableStateOf或忘记by委托。 - 错误:键盘弹出后遮挡输入框 —— 确保 Activity 的
android:windowSoftInputMode设置为adjustResize或adjustPan,并在 Compose 中使用Modifier.imePadding()。 - 调试技巧:在
onValueChange中打印日志,确认每次输入是否触发了正确的状态更新。
完整示例:综合应用
下面是一个包含占位符、过滤、单行、支持不同键盘类型的登录表单片段:
@Composable
fun LoginForm() {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = username,
onValueChange = { username = it },
placeholder = { Text("邮箱或手机号") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email
),
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(12.dp))
TextField(
value = password,
onValueChange = { password = it },
placeholder = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { /* 执行登录 */ }
),
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
}
}
visualTransformation 参数用来改变文本的显示方式而不改变实际文本值。常用的有 PasswordVisualTransformation()(密码掩码)和 VisualTransformation.None。如果你需要自定义变换(如信用卡号分段),可以继承 VisualTransformation 接口。
状态管理对比:remember vs rememberSaveable
当 Activity 重建(如屏幕旋转)时,remember 会丢失状态;rememberSaveable 会将状态保存到 Bundle 中自动恢复。对于文本输入框,推荐使用 rememberSaveable:
var text by rememberSaveable { mutableStateOf("") }
内部实现原理:rememberSaveable 通过 Saver 对象将 String 写入 SavedStateHandle,然后从 Bundle 恢复。对于基本类型(String、Int 等)它可以直接使用;涉及复杂对象需要自定义 Saver。
本章学习了 TextField 的声明式数据流、占位符与单行控制、实时过滤监听、键盘类型与操作按钮配置,以及 remember 与 rememberSaveable 的状态持久化区别。下一阶段,你将接触 OutlinedTextField 与 DecorationBox 更深度的自定义模式。