处理用户输入

TextField 允许用户输入和修改文本。此页面描述了如何实现 TextField、设置 TextField 输入样式以及配置其他 TextField 选项(例如键盘选项和视觉转换用户输入)。

选择 TextField 实现

存在两个级别的 TextField 实现

  1. TextField 是 Material Design 实现。我们建议您选择此实现,因为它遵循 Material Design 指南
  2. BasicTextField 使用户能够通过硬件或软件键盘编辑文本,但不提供提示或占位符之类的装饰。

@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf("Hello") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

An editable text field containing the word

@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

An editable text field, with a purple border and label.

设置 TextField 样式

TextFieldBasicTextField 共享许多用于自定义它们的常用参数。TextField 的完整列表可在 TextField 源代码 中找到。这是一个非详尽的列表,其中包含一些有用的参数

  • 单行 (singleLine)
  • 最大行数 (maxLines)
  • 文本样式 (textStyle)

@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text("Enter text") },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}

A multiline TextField, with two editable lines plus the label

当您的设计需要 Material TextFieldOutlinedTextField 时,我们建议使用 TextField 而不是 BasicTextField。但是,在构建不需要 Material 规范中装饰的设计时,应使用 BasicTextField

使用 Brush API 设置输入样式

您可以使用 Brush API 为您的 TextField 设置更高级的样式。以下部分描述了如何使用 Brush 向 TextField 输入添加彩色渐变。

有关使用 Brush API 设置文本样式的更多信息,请参阅 使用 Brush API 启用高级样式

使用 TextStyle 实现彩色渐变

要在您在 TextField 中键入时实现彩色渐变,请将您选择的笔刷设置为 TextFieldTextStyle。在此示例中,我们使用具有 linearGradient 的内置笔刷来查看在文本键入到 TextField 中时的彩虹渐变效果。

var text by remember { mutableStateOf("") }
val brush = remember {
    Brush.linearGradient(
        colors = rainbowColors
    )
}
TextField(
    value = text, onValueChange = { text = it }, textStyle = TextStyle(brush = brush)
)

Using buildAnnotatedString and SpanStyle, along with linearGradient, to customize only a piece of text.
图 3. 使用 buildAnnotatedStringSpanStyle 以及 linearGradient 来仅自定义一部分文本。

设置键盘选项

TextField 允许您设置键盘配置选项,例如键盘布局,或者如果键盘支持,则启用自动更正。如果软件键盘不符合此处提供的选项,则可能无法保证某些选项。以下是 受支持的键盘选项 列表

  • 大小写 (capitalization)
  • 自动更正 (autoCorrect)
  • 键盘类型 (keyboardType)
  • 输入法动作 (imeAction)

格式化输入

TextField 允许您在输入值上设置 VisualTransformation,例如将字符替换为密码的 *,或为信用卡号每 4 位数字插入连字符

@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf("") }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text("Enter password") },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}

A password text entry field, with the text masked

更多示例可在 VisualTransformationSamples 源代码 中找到。

清理输入

编辑文本时的一项常见任务是去除前导字符,或在每次文本更改时转换输入字符串。

作为模型,您应该假设键盘每次 onValueChange 都可能进行任意的大量编辑。例如,如果用户使用自动更正、用表情符号替换单词或其他智能编辑功能,则可能会发生这种情况。为了正确处理这种情况,请在编写任何转换逻辑时假设传递给 onValueChange 的当前文本与将传递给 onValueChange 的先前值或后续值无关。

要实现禁止前导零的文本字段,您可以通过在每次值更改时去除所有前导零来实现。

@Composable
fun NoLeadingZeroes() {
    var input by rememberSaveable { mutableStateOf("") }
    TextField(
        value = input,
        onValueChange = { newText ->
            input = newText.trimStart { it == '0' }
        }
    )
}

要在清理文本时控制光标位置,请使用 TextFieldValueTextField 作为状态的一部分。

状态最佳实践

以下是一系列最佳实践,用于定义和更新 TextField 状态以防止应用中的输入问题。

  • 使用 MutableState 表示 TextField 状态:避免使用像 StateFlow 这样的反应式流来表示 TextField 状态,因为这些结构可能会引入异步延迟。

class SignUpViewModel : ViewModel() {

    var username by mutableStateOf("")
        private set

    /* ... */
}

  • 避免延迟更新状态:当您调用 onValueChange 时,同步并立即更新您的 TextField

// SignUpViewModel.kt

class SignUpViewModel(private val userRepository: UserRepository) : ViewModel() {

    var username by mutableStateOf("")
        private set

    fun updateUsername(input: String) {
        username = input
    }
}

// SignUpScreen.kt

@Composable
fun SignUpScreen(/*...*/) {

    OutlinedTextField(
        value = viewModel.username,
        onValueChange = { username -> viewModel.updateUsername(username) }
        /*...*/
    )
}

  • 在何处定义状态:如果您的 TextField 状态需要在您键入时进行业务逻辑验证,则将状态提升到您的 ViewModel 是正确的做法。如果不需要,您可以使用可组合函数或状态持有者类作为事实来源。要了解有关在何处提升状态的更多信息,请参阅 状态提升 文档。