22、表单与输入:OutlinedTextField、键盘选项、输入验证
表单开发,说白了就是跟用户「要数据」。我在项目里见过太多因为输入框没处理好,导致用户填到一半崩溃退出的情况。嗯,今天我们就来聊聊 Compose 里怎么把表单做得既好用又靠谱。
22.1 OutlinedTextField 的基本用法
Compose 提供了两种文本输入框:TextField 和 OutlinedTextField。我个人习惯用 OutlinedTextField,因为它有边框,视觉上更清晰,用户一眼就知道哪里可以输入。
@Composable
fun BasicOutlinedTextField() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("用户名") },
placeholder = { Text("请输入用户名") },
singleLine = true
)
}
这里有个细节:value 和 onValueChange 是必须的。为什么?因为 Compose 是声明式 UI,状态由你管理,框架只负责渲染。你想想看,如果 value 不更新,用户敲键盘屏幕不会有任何变化。
remember 包裹状态,避免重组时丢失数据。如果是列表中的输入框,记得用 rememberSaveable 来应对配置变更。
22.2 键盘选项:让输入更智能
键盘类型选对了,用户体验能提升一大截。我曾经在一个金融 App 里看到数字输入框弹出全键盘,用户气得直接卸载了。
Compose 通过 keyboardOptions 参数控制键盘行为:
OutlinedTextField(
value = amount,
onValueChange = { amount = it },
label = { Text("金额") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { /* 处理完成事件 */ }
)
)
| KeyboardType | 适用场景 |
|---|---|
| Text | 普通文本 |
| Number | 纯数字(如金额、年龄) |
| 邮箱地址(带 @ 和 . 快捷键) | |
| Password | 密码输入(自动隐藏字符) |
| Phone | 电话号码(数字键盘 + 拨号键) |
还有 imeAction,它控制键盘右下角的按钮文字。比如搜索框用 ImeAction.Search,登录表单用 ImeAction.Next 跳到下一个输入框。嗯,这里要注意:imeAction 只是改变按钮外观,实际逻辑要写在 keyboardActions 里。
KeyboardType.Number 但用户还能输入小数点。后来发现 Number 类型默认允许小数。如果只要整数,用 NumberPassword 或者自己过滤 onValueChange。
22.3 输入验证:别让用户乱填
验证分两种:实时验证和提交时验证。我个人倾向实时验证,用户边输边提示,体验更好。
22.3.1 实时验证示例
@Composable
fun EmailInput() {
var email by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
val errorMessage = "邮箱格式不正确"
OutlinedTextField(
value = email,
onValueChange = {
email = it
isError = !isValidEmail(it)
},
label = { Text("邮箱") },
isError = isError,
supportingText = {
if (isError) {
Text(text = errorMessage, color = MaterialTheme.colorScheme.error)
}
}
)
}
fun isValidEmail(email: String): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
看到 supportingText 了吗?这是 Compose 1.3 之后推荐的做法,替代了旧的 error 参数。它既能显示错误提示,也能显示普通帮助文字,灵活很多。
22.3.2 表单整体验证
多个输入框时,我习惯用一个状态类统一管理:
data class FormState(
val username: String = "",
val password: String = "",
val usernameError: String? = null,
val passwordError: String? = null,
val isSubmitting: Boolean = false
)
@Composable
fun LoginForm() {
var formState by remember { mutableStateOf(FormState()) }
fun validate(): Boolean {
var valid = true
formState = formState.copy(
usernameError = if (formState.username.length < 3) "用户名至少3个字符" else null,
passwordError = if (formState.password.length < 6) "密码至少6个字符" else null
)
valid = formState.usernameError == null && formState.passwordError == null
return valid
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = formState.username,
onValueChange = { formState = formState.copy(username = it) },
label = { Text("用户名") },
isError = formState.usernameError != null,
supportingText = { formState.usernameError?.let { Text(it) } }
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
isError = formState.passwordError != null,
supportingText = { formState.passwordError?.let { Text(it) } }
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
if (validate()) {
// 执行登录
}
},
enabled = !formState.isSubmitting
) {
Text("登录")
}
}
}
data class 管理表单状态,每个字段的错误信息独立存储。这样验证逻辑清晰,UI 更新也精准——哪个字段错了就只显示哪个的错误。
22.4 视觉反馈与焦点管理
用户输入时,焦点切换和视觉反馈很重要。我记得有个项目,用户填完密码按回车,键盘没消失,也没跳到登录按钮,用户以为卡死了。
val focusManager = LocalFocusManager.current
OutlinedTextField(
// ...
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = { focusManager.clearFocus() }
)
)
用 LocalFocusManager 可以手动控制焦点。比如登录成功后自动收起键盘,或者按「下一项」时跳到下一个输入框。
颜色方面,OutlinedTextField 默认使用主题色。你可以通过 colors 参数自定义:
OutlinedTextField(
// ...
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color.Blue,
unfocusedBorderColor = Color.Gray,
errorBorderColor = Color.Red,
cursorColor = Color.Blue
)
)
22.5 知识体系总览
下面这张图总结了本章的核心内容,你可以对照着回顾:
22.6 实战建议
最后分享几个我在项目中沉淀下来的经验:
- 状态提升: 把表单状态提到 ViewModel 里,用
StateFlow管理。这样屏幕旋转不会丢数据,测试也方便。 - 防抖处理: 实时验证时,比如检查用户名是否被注册,加个 300ms 的防抖。不然用户每敲一个字母就发一次请求,服务器会报警的。
- 无障碍支持: 给输入框加
contentDescription,视障用户靠读屏软件也能操作。 - 错误提示要友好: 别说「格式错误」,要说「邮箱地址需要包含 @ 符号」。用户不是程序员,看不懂正则表达式。
嗯,表单开发看似简单,但细节决定成败。把上面这些点都照顾到了,你的 App 输入体验绝对能甩开大多数竞品。