OutlinedTextField 装饰:边框样式、标签、错误提示、密码输入掩码
OutlinedTextField 是 Material3 中带外边框的文本输入组件,通过装饰参数可实现边框样式定制、浮动标签、错误提示、密码掩码等常见交互。本章从声明式 UI 视角拆解其装饰层结构,并给出可运行代码片段与布局流程示意图。
装饰层结构
OutlinedTextField 的视觉层从外到内依次为:
- 边框容器(outline)—— 由
border参数控制,支持实色、渐变、圆角和描边 - 浮动标签(label)—— 输入框获取焦点或内容非空时上浮至边框上方
- 输入文本区域(value / onValueChange)
- 尾部装饰(trailingIcon,如密码可见切换按钮)
- 提示文本(supportingText)—— 常用来展示错误信息或帮助说明
核心声明式原则:所有装饰状态均由外部数据驱动——错误状态由
isError 字段决定,边框颜色和标签位置由焦点状态与内容非空状态共同决定。
边框样式定制
border 参数接收 BorderStroke 对象,可设置宽度、颜色和形状。若需与焦点状态联动,推荐使用 TextFieldDefaults.outlinedBorder 并覆盖颜色。
@Composable
fun OutlinedTextFieldBorderDemo() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("自定义边框") },
border = BorderStroke(
width = 2.dp,
color = if (text.length > 5) Color.Red else Color(0xFF3498db)
)
)
}
提示:使用
TextFieldDefaults.outlinedBorder 配合 enabled/isError 参数可获得 Material 标准焦点边框动画。自定义 border 会覆盖默认焦点效果。
标签与浮动行为
- 固定标签:
placeholder—— 始终显示在框内,不浮动 - 浮动标签:
label—— 输入框聚焦或内容非空时上移
标签通过 label 参数传入 Composables 节点,支持字体颜色、大小等样式。标签位置由系统根据焦点状态和 value 是否为空自动计算。
错误提示
通过 isError 控制错误状态,supportingText 展示错误文案。边框颜色自动切换为错误色(Material3 中为 Color(0xFFB3261E))。
@Composable
fun EmailErrorDemo() {
var email by remember { mutableStateOf("") }
val isError = email.isNotEmpty() && !email.contains("@")
OutlinedTextField(
value = email,
onValueChange = { email = it },
isError = isError,
label = { Text("邮箱") },
supportingText = {
if (isError) {
Text(
text = "请输入有效的邮箱地址",
color = MaterialTheme.colorScheme.error
)
}
}
)
}
注意:错误提示文本不应仅依赖颜色传达信息,应同时提供文字描述以保证无障碍。如果错误文本较长,建议搭配
maxLines 防止截断。
密码输入掩码
使用 visualTransformation 参数将输入字符替换为圆点或星号。配合 trailingIcon 实现密码可见切换。
@Composable
fun PasswordFieldDemo() {
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
visualTransformation = if (passwordVisible) {
VisualTransformation.None
} else {
PasswordVisualTransformation()
},
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector = if (passwordVisible) {
Icons.Default.VisibilityOff
} else {
Icons.Default.Visibility
},
contentDescription = if (passwordVisible) "隐藏密码" else "显示密码"
)
}
}
)
}
综合示例:含验证的登录表单
将边框样式、错误提示、标签、密码掩码整合在一个表单中,演示状态联动。
@Composable
fun LoginForm() {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val usernameError = username.isNotEmpty() && username.length < 3
val passwordError = password.isNotEmpty() && password.length < 6
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") },
isError = usernameError,
supportingText = {
if (usernameError) Text("用户名至少3个字符")
},
singleLine = true
)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
isError = passwordError,
supportingText = {
if (passwordError) Text("密码至少6位")
},
trailingIcon = {
// 可在此添加可见切换按钮(代码略)
}
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { /* 登录逻辑 */ },
enabled = username.length >= 3 && password.length >= 6
) {
Text("登录")
}
}
}
常见错误调试
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 标签不浮动 | 使用了 placeholder 而非 label | 将 placeholder 替换为 label |
| 边框颜色不随错误改变 | 自定义 border 覆盖了默认错误状态 | 移除自定义 border,下放给 isError 控制 |
| 密码可见切换后布局闪烁 | VisualTransformation 切换导致重组 | 使用 remember 缓存变换对象,避免每次重建 |
| 错误提示被截断 | supportingText 未设置 maxLines | 设置 maxLines = 2 并配合 overflow = TextOverflow.Ellipsis |
性能要点:
visualTransformation 在每次输入变化时重新计算字符映射。对于高频输入场景,考虑使用 KeyboardActions 延迟处理,或采用 BasicTextField 搭配自定义变换逻辑。