OutlinedTextField 装饰:边框样式、标签、错误提示、密码输入掩码

OutlinedTextField 是 Material3 中带外边框的文本输入组件,通过装饰参数可实现边框样式定制、浮动标签、错误提示、密码掩码等常见交互。本章从声明式 UI 视角拆解其装饰层结构,并给出可运行代码片段与布局流程示意图。

装饰层结构

OutlinedTextField 的视觉层从外到内依次为:

  • 边框容器(outline)—— 由 border 参数控制,支持实色、渐变、圆角和描边
  • 浮动标签(label)—— 输入框获取焦点或内容非空时上浮至边框上方
  • 输入文本区域(value / onValueChange)
  • 尾部装饰(trailingIcon,如密码可见切换按钮)
  • 提示文本(supportingText)—— 常用来展示错误信息或帮助说明
核心声明式原则:所有装饰状态均由外部数据驱动——错误状态由 isError 字段决定,边框颜色和标签位置由焦点状态与内容非空状态共同决定。
OutlinedTextField 装饰层结构 1. 边框容器 (border) 2. 浮动标签 (label) 3. 输入文本区 (value) 4. 尾部图标 (trailingIcon) 5. 支持文本 (supportingText):错误提示 isError = true → 边框变红 + 显示错误文本 6. 密码掩码 (visualTransformation = PasswordVisualTransformation()) border: BorderStroke label: @Composable

边框样式定制

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 搭配自定义变换逻辑。