配置文本字段

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

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

功能

基于值的文本字段

基于状态的文本字段

基于状态的福利

状态管理

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

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

  • 移除了 onValueChange 回调,这可防止您引入异步行为。
  • 该状态在重组、配置更改和进程终止后仍会保留。

可视化转换

使用 VisualTransformation 修改所显示文本的外观。这通常会在一个步骤中同时处理输入和输出格式设置。

使用 InputTransformation 在用户输入提交到状态之前对其进行修改,并使用 OutputTransformation 在不更改底层状态数据的情况下设置文本字段内容的格式。

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

行数限制

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

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

  • 通过提供 TextFieldLineLimits 类型的 lineLimits 参数,消除配置行数限制时的歧义。

安全文本字段

不适用

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

TextField 和 BasicTextField 共用许多可对它们进行自定义的常用参数。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 TextField 或 OutlinedTextField,我们建议您使用 TextField,而不是 BasicTextField。但是,在构建无需 Material 规范中的装饰的设计时,应使用 BasicTextField。

配置线路限制

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

使用 TextFieldLineLimits 为 TextField 选择合适的线路配置:

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

包含文本

SingleLine 配置具有以下特征:

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

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

一个多行文本字段,其中包含

MultiLine 配置具有以下特征:

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

使用 Brush API 设置输入样式

您可以使用 Brush API 在 TextField 中实现更高级的样式。 以下部分介绍了如何使用 Brush 为 TextField 输入添加颜色渐变。

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

使用 TextStyle 实现彩色渐变

如需在 TextField 中输入内容时实现彩色渐变,请将您选择的画笔设置为 TextField 的 TextStyle。在此示例中,我们使用内置画笔和 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)
)

在文本字段中输入的文本,显示彩虹渐变效果。
图 1. TextField 内容的彩虹渐变效果。

管理文本字段状态

TextField 使用名为 TextFieldState 的专用状态容器类来存储其内容和选择。TextFieldState 旨在提升到架构中的任何合适位置。TextFieldState 提供了 2 个主要属性:

  • initialText:TextField 的内容。
  • 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,其中显示了文本“Username”。
图 2. TextField,初始文本为“Username”。

使用 TextFieldBuffer 修改文字

TextFieldBuffer 用作可编辑的文本容器,功能类似于 StringBuilder。它同时包含文本内容和所选内容的相关信息。

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

您可以使用标准编辑功能(例如 append、insert、replace 或 delete)来修改缓冲区的内容。如需更改选择状态,请直接设置其 selection: TextRange 变量,或使用 placeCursorAtEnd 或 selectAll 等实用函数。所选内容本身由 TextRange 表示,其中起始索引包含在内,结束索引不包含在内。 如果 TextRange 的起始值和结束值相同(例如 (3, 3)),则表示光标位置,未选择任何字符。

val phoneNumberState = rememberTextFieldState("1234567890")

TextField(
    state = phoneNumberState,
    keyboardOptions = KeyboardOptions(
        keyboardType = KeyboardType.Phone
    ),
    inputTransformation = InputTransformation.maxLength(10).then {
        if (!asCharSequence().isDigitsOnly()) {
            revertAllChanges()
        }
    },
    outputTransformation = OutputTransformation {
        if (length > 0) insert(0, "(")
        if (length > 4) insert(4, ")")
        if (length > 8) insert(8, "-")
    }
)

在TextFieldState中修改文本

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

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

    // Initial textFieldState text passed in is "I love Android"
    // textFieldState.text : I love Android
    // textFieldState.selection: TextRange(14, 14)
    textFieldState.edit { insert(14, "!") }
    // textFieldState.text : I love Android!
    // textFieldState.selection: TextRange(15, 15)
    textFieldState.edit { replace(7, 14, "Compose") }
    // textFieldState.text : I love Compose!
    // textFieldState.selection: TextRange(15, 15)
    textFieldState.edit { append("!!!") }
    // textFieldState.text : I love Compose!!!!
    // textFieldState.selection: TextRange(18, 18)
    textFieldState.edit { selectAll() }
    // textFieldState.text : I love Compose!!!!
    // textFieldState.selection: TextRange(0, 18)

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

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

  • clearText:清除所有文本。

    textFieldState.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 (!asCharSequence().isDigitsOnly()) {
            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.Next 或 ImeAction.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:隐藏除最后一个字符以外的所有输入。移动设备上的默认行为。

    一个安全文本字段,其中仅会短暂显示最后输入的字符。

其他资源