配置文本字段

TextField 允许用户输入和修改文本。您可以使用两种类型的文本字段:基于状态的文本字段基于值的文本字段。选择您要显示内容的类型:

我们建议您使用基于状态的文本字段,因为它们提供了一种更完整、更可靠的方法来管理 TextField 的状态。下表概述了这些类型的文本字段之间的差异,并包含基于状态的文本字段提供的主要优势:

功能

基于值的文本字段

基于状态的文本字段

基于州/省/自治区/直辖市的福利

状态管理

使用 onValueChange 回调更新文本字段状态。您负责根据 onValueChange 报告的更改更新您自己的状态中的 value

明确使用 TextFieldState 对象来管理文本输入状态(值、选择、组合)。此状态可以被记住和共享。

  • 移除了 onValueChange 回调,这会阻止您引入异步行为。
  • 该状态在重组、配置和进程终止后仍然有效。

视觉转换

使用 VisualTransformation 修改显示文本的显示方式。这通常会在单个步骤中处理输入和输出格式设置。

使用 InputTransformation 在用户输入提交到状态之前修改该输入,并使用 OutputTransformation 设置文本字段内容的格式,而不会更改底层状态数据。

  • 您无需再使用 OutputTransformation 提供原始原始文本与转换文本之间的偏移量映射。

行数限制

接受 singleLine: Boolean, maxLines: IntminLines: Int 以控制行数。

使用 lineLimits: TextFieldLineLimits 配置文本字段可占用的行数下限和上限。

  • 通过提供类型为 TextFieldLineLimitslineLimits 参数,消除了配置行限制时的模糊性。

安全文本字段

不适用

SecureTextField 是在基于状态的文本字段之上构建的可组合项,用于编写密码字段。

  • 可让您优化底层安全性,并附带 textObfuscationMode 预定义界面。

本页介绍了如何实现 TextField、设置 TextField 输入的样式,以及配置其他 TextField 选项(例如键盘选项和视觉转换用户输入)。

选择 TextField 实现

TextField 实现分为两个级别:

  1. TextField 是 Material Design 实现。我们建议您选择此实现,因为它遵循的是 Material Design 指南
    • 默认样式为填充
    • OutlinedTextField轮廓样式版本
  2. BasicTextField 允许用户通过硬件或软件键盘编辑文本,但没有提供提示或占位符等装饰。

TextField(
    state = rememberTextFieldState(initialText = "Hello"),
    label = { Text("Label") }
)

包含“

OutlinedTextField(
    state = rememberTextFieldState(),
    label = { Text("Label") }
)

可编辑的文字字段,带有紫色边框和标签。

样式 TextField

TextFieldBasicTextField 共用许多可用于自定义的常用参数。如需查看 TextField 的完整列表,请参阅 TextField 源代码。以下列出了部分有用的参数,但并非详尽无遗:

  • textStyle
  • lineLimits

TextField(
    state = rememberTextFieldState("Hello\nWorld\nInvisible"),
    lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
    placeholder = { Text("") },
    textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
    label = { Text("Enter text") },
    modifier = Modifier.padding(20.dp)
)

多行 TextField,包括两个可编辑的行以及标签

如果您的设计调用 Material TextFieldOutlinedTextField,建议您使用 TextField 而不是 BasicTextField。但是,在构建无需 Material 规范中的装饰的设计时,应使用 BasicTextField

配置线条限制

TextField 可组合项支持沿单个轴滚动。滚动行为由 lineLimits 参数决定。配置为单行滚动的 TextField 会水平滚动,而多行 TextField 会垂直滚动。

使用 TextFieldLineLimitsTextField 选择适当的行配置:

TextField(
    state = rememberTextFieldState(),
    lineLimits = TextFieldLineLimits.SingleLine
)

包含文本“

SingleLine 配置具有以下特征:

  • 文本不会换行,也不允许换行。
  • TextField 始终具有固定的高度。
  • 如果文本溢出,则会水平滚动。

TextField(
    state = rememberTextFieldState("Hello\nWorld\nHello\nWorld"),
    lineLimits = TextFieldLineLimits.MultiLine(1, 4)
)

包含文本

MultiLine 配置具有以下特征:

  • 接受两个参数:minHeightInLinesmaxHeightInLines
  • 文本字段的高度至少为 minHeightInLines
  • 如果文本溢出,则会换行。
  • 如果文本需要更多行,该字段会一直扩大,直到高度达到 maxHeightInLines,然后垂直滚动。

使用 Brush API 设置输入样式

您可以使用 Brush APITextField 中实现更高级的样式设置。以下部分介绍了如何使用画笔向 TextField 输入添加彩色渐变。

如需详细了解如何使用 Brush API 设置文本样式,请参阅使用 Brush API 启用高级样式

使用 TextStyle 实现彩色渐变

如需在 TextField 中输入内容时实现彩色渐变,请将所选画笔设置为 TextFieldTextStyle。在此示例中,我们将内置画笔与 linearGradient 结合使用,以便在向 TextField 中输入文本时查看彩虹渐变效果。

val brush = remember {
    Brush.linearGradient(
        colors = listOf(Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Magenta)
    )
}
TextField(
    state = rememberTextFieldState(), textStyle = TextStyle(brush = brush)
)

使用 buildAnnotatedString 和 SpanStyle 以及 linearGradient 仅自定义一段文本。
图 1. TextField 内容的彩虹渐变效果。

管理文本字段状态

TextField 使用名为 TextFieldState 的专用状态容器类来存储其内容和当前选择。TextFieldState 旨在根据架构中的适用位置进行提升。TextFieldState 提供 2 个主要属性:

  • initialTextTextField 的内容。
  • initialSelection:指示光标或所选内容当前的位置。

TextFieldState 与其他方法(例如 onValueChange 回调)的区别在于,TextFieldState 会完全封装整个输入流程。这包括使用正确的后备数据结构、内嵌过滤器和格式设置程序,以及同步来自不同来源的所有修改。

您可以使用 TextFieldState()TextField 中提升状态。为此,我们建议使用 rememberTextFieldState() 函数。rememberTextFieldState() 会在可组合项中创建 TextFieldState 实例,确保记住状态对象,并提供内置的保存和恢复功能:

val usernameState = rememberTextFieldState()
TextField(
    state = usernameState,
    lineLimits = TextFieldLineLimits.SingleLine,
    placeholder = { Text("Enter Username") }
)

rememberTextFieldState 可以使用空参数,也可以传入初始值来表示初始化时的文本值。如果在后续重组中传递了其他值,则不会更新状态的值。如需在状态初始化后更新状态,请对 TextFieldState 调用修改方法。

TextField(
    state = rememberTextFieldState(initialText = "Username"),
    lineLimits = TextFieldLineLimits.SingleLine,
)

一个 TextField,文本字段中显示“用户名”文本。
图 2. TextField,初始文本为“用户名”。

使用 TextFieldBuffer 修改文本

TextFieldBuffer 可用作可编辑的文本容器,其功能与 StringBuilder 类似。它既包含文本内容,也包含当前所选内容的相关信息。

您经常会在 TextFieldState.editInputTransformation.transformInputOutputTransformation.transformOutput 等函数中遇到 TextFieldBuffer 作为接收器作用域。在这些函数中,您可以根据需要读取或更新 TextFieldBuffer。之后,这些更改会提交到 TextFieldState,或者在 OutputTransformation 的情况下传递给渲染流水线。

您可以使用标准编辑函数(例如 appendinsertreplacedelete)修改缓冲区的内容。如需更改选择状态,请直接设置其 selection: TextRange 变量,或使用 placeCursorAtEndselectAll 等实用函数。选择本身由 TextRange 表示,其中开始索引包含边界值,结束索引不含边界值。具有相同开始值和结束值的 TextRange(例如 (3, 3))表示当前没有选中任何字符的光标位置。

val phoneNumberState = rememberTextFieldState()

LaunchedEffect(phoneNumberState) {
    phoneNumberState.edit { // TextFieldBuffer scope
        append("123456789")
    }
}

TextField(
    state = phoneNumberState,
    inputTransformation = InputTransformation { // TextFieldBuffer scope
        if (asCharSequence().isDigitsOnly()) {
            revertAllChanges()
        }
    },
    outputTransformation = OutputTransformation {
        if (length > 0) insert(0, "(")
        if (length > 4) insert(4, ")")
        if (length > 8) insert(8, "-")
    }
)

修改 TextFieldState 中的文本

您可以通过以下几种方法直接通过状态变量修改状态:

  • edit:可让您修改状态内容,并提供 TextFieldBuffer 函数,以便您使用 insertreplaceappend 等方法。

    val usernameState = rememberTextFieldState("I love Android")
    // textFieldState.text : I love Android
    // textFieldState.selection: TextRange(14, 14)
    usernameState.edit { insert(14, "!") }
    // textFieldState.text : I love Android!
    // textFieldState.selection: TextRange(15, 15)
    usernameState.edit { replace(7, 14, "Compose") }
    // textFieldState.text : I love Compose!
    // textFieldState.selection: TextRange(15, 15)
    usernameState.edit { append("!!!") }
    // textFieldState.text : I love Compose!!!!
    // textFieldState.selection: TextRange(18, 18)
    usernameState.edit { selectAll() }
    // textFieldState.text : I love Compose!!!!
    // textFieldState.selection: TextRange(0, 18)

  • setTextAndPlaceCursorAtEnd:清除当前文本,将其替换为指定文本,并将光标设置在末尾。

    usernameState.setTextAndPlaceCursorAtEnd("I really love Android")
    // textFieldState.text : I really love Android
    // textFieldState.selection : TextRange(21, 21)

  • clearText:清除所有文本。

    usernameState.clearText()
    // textFieldState.text :
    // textFieldState.selection : TextRange(0, 0)

如需了解其他 TextFieldState 函数,请参阅 TextFieldState 参考文档

修改用户输入

以下部分介绍了如何修改用户输入。借助输入转换,您可以在用户输入时滤除 TextField 输入,而输出转换则会在用户输入显示在屏幕上之前设置其格式。

使用输入转换过滤用户输入

借助输入转换,您可以过滤用户的输入。例如,如果您的 TextField 接受美国电话号码,您只想接受 10 位数字。InputTransformation 的结果会保存在 TextFieldState 中。

系统提供了适用于常见 InputTransformation 用例的内置过滤条件。如需限制长度,请调用 InputTransformation.maxLength()

TextField(
    state = rememberTextFieldState(),
    lineLimits = TextFieldLineLimits.SingleLine,
    inputTransformation = InputTransformation.maxLength(10)
)

自定义输入转换

InputTransformation 是一个单函数接口。实现自定义 InputTransformation 时,您需要替换 TextFieldBuffer.transformInput

class CustomInputTransformation : InputTransformation {
    override fun TextFieldBuffer.transformInput() {
    }
}

对于电话号码,请添加一个自定义输入转换,以便仅允许在 TextField 中输入数字:

class DigitOnlyInputTransformation : InputTransformation {
    override fun TextFieldBuffer.transformInput() {
        if (!TextUtils.isDigitsOnly(asCharSequence())) {
            revertAllChanges()
        }
    }
}

链接输入转换

如需对文本输入添加多个过滤器,请使用 then 扩展函数串联 InputTransformation。过滤器会按顺序执行。最佳实践是先应用最具选择性的过滤条件,以免对最终会被滤除的数据进行不必要的转换。

TextField(
    state = rememberTextFieldState(),
    inputTransformation = InputTransformation.maxLength(6)
        .then(CustomInputTransformation()),
)

添加输入转换后,TextField 输入最多接受 10 位数字。

在输入内容显示之前设置其格式

借助 OutputTransformation,您可以在用户输入内容在屏幕上呈现之前对其进行格式设置。与 InputTransformation 不同,通过 OutputTransformation 进行的格式设置不会保存在 TextFieldState 中。基于上一个电话号码示例,您需要在适当的位置添加圆括号和短划线:

美国电话号码,格式正确,包含括号、短划线和相应的编号。
图 3. 格式正确且包含相应索引的美国电话号码。

这是在基于值的 TextField 中处理 VisualTransformation 的更新方式,主要区别在于您无需计算其偏移量映射。

OutputTransformation 是单一抽象方法接口。如需实现自定义 OutputTransformation,您需要替换 transformOutput 方法:

class CustomOutputTransformation : OutputTransformation {
    override fun TextFieldBuffer.transformOutput() {
    }
}

如需设置电话号码格式,请在 OutputTransformation 的索引 0 处添加左括号,在索引 4 处添加右括号,并在索引 8 处添加短划线:

class PhoneNumberOutputTransformation : OutputTransformation {
    override fun TextFieldBuffer.transformOutput() {
        if (length > 0) insert(0, "(")
        if (length > 4) insert(4, ")")
        if (length > 8) insert(8, "-")
    }
}

接下来,将 OutputTransformation 添加到 TextField

TextField(
    state = rememberTextFieldState(),
    outputTransformation = PhoneNumberOutputTransformation()
)

转换如何协同发挥作用

下图显示了从文本输入到转换再到输出的流程:

直观展示文本输入在转换为文本输出之前的流程。
图 4. 一张示意图,显示文本输入在转换为文本输出之前的流程。
  1. 从输入源接收输入。
  2. 输入通过 InputTransformation 进行过滤,并保存在 TextFieldState 中。
  3. 输入会通过 OutputTransformation 进行格式设置。
  4. 输入显示在 TextField 中。

设置键盘选项

借助 TextField,您可以设置键盘配置选项(例如键盘布局),或启用自动更正(如果键盘支持的话)。如果软件键盘不符合此处提供的选项,则无法保证某些选项的可用性。下面列出了支持的键盘选项

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

KeyboardOptions 类现在包含一个新的布尔参数 showKeyboardOnFocus,您可以专门将其用于与 TextFieldState 集成的 TextField 组件。此选项用于控制在 TextField 通过非直接用户互动方式(例如程序化方式)获取焦点时软件键盘的行为。

KeyboardOptions.showKeyboardOnFocus 设置为 true 后,如果 TextField 间接获得焦点,软件键盘不会自动显示。在这种情况下,用户需要明确点按 TextField 本身才能显示键盘。

定义键盘互动逻辑

Android 软件键盘上的操作按钮可在应用中提供交互式响应。如需详细了解如何配置操作按钮,请参阅设置键盘选项部分。

软件键盘操作按钮(对勾图标)用红色圈出。
图 5. 软件键盘操作按钮。

如需定义用户点按此操作按钮时会发生什么,请使用 onKeyboardAction 参数。此参数接受一个名为 KeyboardActionHandler 的可选函数接口。KeyboardActionHandler 接口包含单个方法 onKeyboardAction(performDefaultAction: () -> Unit)。通过为此 onKeyboardAction 方法提供实现,您可以引入在用户按键盘的操作按钮时执行的自定义逻辑。

多种标准键盘操作类型都带有内置的默认行为。例如,默认情况下,选择 ImeAction.NextImeAction.Previous 作为操作类型会将焦点分别移至后续或前面的输入字段。同样,设置为 ImeAction.Done 的操作按钮通常会关闭软件键盘。这些默认功能会自动执行,而无需您提供 KeyboardActionHandler

除了这些默认操作之外,您还可以实现自定义行为。当您提供 KeyboardActionHandler 时,其 onKeyboardAction 方法会收到 performDefaultAction 函数。您可以在自定义逻辑中的任何时间点调用此 performDefaultAction() 函数,以触发与当前 IME 操作关联的标准默认行为。

TextField(
    state = textFieldViewModel.usernameState,
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
    onKeyboardAction = { performDefaultAction ->
        textFieldViewModel.validateUsername()
        performDefaultAction()
    }
)

以下代码段展示了包含用户名字段的注册屏幕的常见用例。对于此字段,其键盘操作按钮已选择 ImeAction.Next。此选项可让用户快速、顺畅地导航到后续的密码字段。

除了此标准导航之外,系统还要求在用户继续输入密码时,为用户名启动后台验证流程。为了确保保留 ImeAction.Next 固有的默认焦点切换行为以及此自定义验证逻辑,系统会调用 performDefaultAction() 函数。调用 performDefaultAction() 会隐式触发底层焦点管理系统,将焦点移至下一个合适的界面元素,从而保留预期的导航流程。

创建安全的密码字段

SecureTextField 是在基于状态的文本字段之上构建的可组合项,用于编写密码字段。我们建议您使用 SecureTextField 创建密码文本字段,因为它默认会隐藏字符输入,并会停用剪切和复制操作。

SecureTextField 有一个 textObfuscationMode,用于控制用户看到字符输入的方式。textObfuscationMode 具有以下选项:

  • Hidden:隐藏所有输入。桌面平台上的默认行为。

  • Visible:显示所有输入。

  • RevealLastTyped:隐藏除最后一个字符以外的所有输入。移动设备上的默认行为。

其他资源