第3章 输入框组件实现:OutlinedTextField定制、键盘类型设置、焦点管理、输入过滤器

支付页面中,输入框承担卡号、金额、CVV等关键数据的采集任务。Compose 的 OutlinedTextField 提供了足够的定制空间,但需要结合支付场景对键盘类型、焦点流动、输入格式做精细化控制。本章从原理到代码,逐一解锁这些能力。

3.1 核心原理

OutlinedTextField 的本质是一个围绕 BasicTextField 包装的 Material 组件,其状态持有(value / onValueChange)、视觉层(Modifier 链 + 外观参数)与行为层(键盘选项、焦点、过滤器)分离。在支付页面中,我们重点操作以下模块:

  • 键盘类型:通过 KeyboardOptions(keyboardType) 指定数字、电话、密码键盘。
  • 焦点管理:利用 FocusRequester 和 FocusManager 实现顺序跳转和失焦验证。
  • 输入过滤器:在 onValueChange 中做正则校验或字符白名单,阻止无效输入进入状态。
性能要点: 避免在 onValueChange 中执行昂贵操作(如网络请求或频繁正则编译)。使用 remember { Regex() } 缓存正则实例。

3.2 实现流程

以支付页面常见的“金额输入框”为例,按以下步骤构建:

  1. 创建状态:var amount by remember { mutableStateOf("") }
  2. 定义过滤器:允许数字和小数点,但不允许多个小数点或非数字字符。
  3. 配置键盘:KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next)
  4. 焦点控制:声明 val focusRequester = remember { FocusRequester() },绑定到 Modifier.focusRequester(focusRequester),并在 LaunchedEffect 中请求焦点。
  5. 美化外观:通过 OutlinedTextField 的 colors、shape、label 参数对齐品牌风格。
关键代码:金额输入框完整实现
@Composable
fun AmountInput(
    onAmountChanged: (String) -> Unit,
    focusRequester: FocusRequester
) {
    var amount by remember { mutableStateOf("") }
    val filter = remember {
        { input: String ->
            input.isEmpty() || Regex("^\\d*\\.?\\d{0,2}$").matches(input)
        }
    }

    OutlinedTextField(
        value = amount,
        onValueChange = { newValue ->
            if (filter(newValue)) {
                amount = newValue
                onAmountChanged(newValue)
            }
        },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Decimal,
            imeAction = ImeAction.Next
        ),
        keyboardActions = KeyboardActions(
            onNext = { /* 跳转到下一个输入框 */ }
        ),
        singleLine = true,
        label = { Text("金额") },
        modifier = Modifier
            .fillMaxWidth()
            .focusRequester(focusRequester)
            .focusProperties { next = focusRequester },
        colors = OutlinedTextFieldDefaults.colors(
            focusedBorderColor = MaterialTheme.colorScheme.primary,
            unfocusedBorderColor = MaterialTheme.colorScheme.outline
        ),
        shape = RoundedCornerShape(8.dp)
    )
}

3.3 焦点管理深入

支付表单通常有多个输入框(卡号、有效期、CVV),需要使用 FocusRequester 链式控制跳转。核心步骤:

  • 为每个输入框创建独立的 FocusRequester 实例。
  • 在 KeyboardActions(onNext) 中调用下一个 focusRequester.requestFocus()。
  • 在 LaunchedEffect(Unit) 中对第一个输入框自动请求焦点。
  • 注意:FocusRequester 必须在 Composable 作用域内通过 remember 创建,且不能在同一层级重复使用同一实例。

3.4 输入过滤器常见模式

场景正则/策略说明
仅数字^\\d*$卡号、CVV
数字 + 小数点后两位^\\d*\\.?\\d{0,2}$金额
禁止空格!newValue.contains(" ")密码、卡号分段
最大长度newValue.length <= maxLen防止超长输入
避免无限重组: 不要直接在 onValueChange 中创建新 Regex 对象或在 LaunchedEffect 外调用 focusRequester.requestFocus(),否则会引起重组风暴。

3.5 内联架构图

下图展示了输入框组件在支付页面中的关键数据流与控制点:

OutlinedTextField 支付输入框组件架构 用户输入事件 输入过滤器 状态更新 键盘类型设置 焦点管理 重组 UI 过滤规则 正则验证 长度限制 字符黑/白名单

3.6 调试与常见错误

调试方法

  • 使用 Layout Inspector 观察焦点颜色变化,确认 focusRequester 生效。
  • 在 onValueChange 中打印原始输入,检查过滤器是否误杀合法字符。
  • 利用 Compose Compiler Metrics 检查重组次数,定位不必要的重组。

常见错误

  • 键盘类型不生效:确保 keyboardOptions 以参数形式传入,而不是在 Modifier 中设置(不生效)。
  • 焦点丢失:FocusRequester 未与 Modifier.focusRequester() 绑定,或同一实例被多个输入框共用。
  • 输入过滤器导致卡顿:在 onValueChange 中创建了新的 Regex 对象。使用 remember 缓存正则。
  • 小数点输入异常:国际键盘差异(逗号 vs 小数点)。统一在过滤器中替换逗号为点。

3.7 性能与稳定性优化

优化点实现方式收益
状态延迟初始化使用 mutableStateOf 配合 derivedStateOf减少额外重组
过滤器预编译remember { Regex(pattern) }避免重复编译
焦点委托延迟在 LaunchedEffect 中请求焦点,而非直接调用确保布局完成
防抖动输入使用 snapshotFlow 结合 debounce减少实时校验频率
核心原则: 输入框组件的稳定性取决于状态管理的 purity(纯函数性)。所有副作用(焦点请求、日志、网络)必须放到 LaunchedEffect 或 snapshotFlow 中处理。

通过本节的实现,你已经掌握了支付页面核心输入框的定制方法。下一部分将把这些输入框组合成完整的表单,并加入验证逻辑。