You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter如何实现类似PayPal金额输入的TextField格式化效果

Flutter 定制占位填充式金额输入框实现方案

直接通过自定义TextEditingController实现即可,不需要依赖第三方包,可控性最高,能完美对齐PayPal输入交互、支持光标移动导航。

核心实现逻辑

  • 固定展示模板为--,--,逗号位置固定不随输入改动,-作为占位符,输入数字从右往左逐位替换占位符,首次输入数字1时即展示为--,-1,和需求完全匹配
  • 所有文本更新、光标位置计算、输入校验逻辑全部封装在控制器内部,避免外部刷新导致的光标跳位问题
  • 仅允许数字输入,退格时自动将对应位置还原为占位符,禁止修改逗号位置
  • 控制器对外直接输出整数类型的实际输入值,不需要额外解析展示文本

具体实现代码

1. 自定义输入控制器

class PlaceholderAmountController extends TextEditingController {
  // 固定展示模板,可按需修改长度适配更大金额输入
  static const String _template = '--,--';
  static const int _maxInputLength = 4;
  int _realValue = 0;

  // 对外获取实际输入的整数值
  int get realInputValue => _realValue;

  @override
  set value(TextEditingValue newValue) {
    // 初始化默认值
    if (text.isEmpty && newValue.text == _template) {
      super.value = newValue.copyWith(
        text: _template,
        selection: const TextSelection.collapsed(offset: _template.length),
      );
      return;
    }

    // 提取纯数字内容
    final int oldDigitCount = text.replaceAll(RegExp(r'[^0-9]'), '').length;
    final int newDigitCount = newValue.text.replaceAll(RegExp(r'[^0-9]'), '').length;
    String rawDigits = newValue.text.replaceAll(RegExp(r'[^0-9]'), '');

    // 处理退格场景:删除最后一位有效数字
    if (newDigitCount < oldDigitCount) {
      rawDigits = rawDigits.isEmpty ? '' : rawDigits.substring(0, rawDigits.length - 1);
    }

    // 超出最大长度截断
    if (rawDigits.length > _maxInputLength) {
      rawDigits = rawDigits.substring(rawDigits.length - _maxInputLength);
    }
    _realValue = rawDigits.isEmpty ? 0 : int.parse(rawDigits);

    // 从右往左逐位替换占位符生成展示文本
    String displayText = _template;
    int digitIndex = rawDigits.length - 1;
    for (int i = _template.length - 1; i >= 0; i--) {
      if (_template[i] == ',') continue;
      if (digitIndex >= 0) {
        displayText = displayText.replaceRange(i, i+1, rawDigits[digitIndex]);
        digitIndex--;
      } else {
        break;
      }
    }

    // 计算光标位置,支持用户手动移动光标后的输入适配
    int cursorOffset = displayText.length;
    for (int i = 0; i < displayText.length; i++) {
      if (displayText[i] == '-') {
        // 优先保留用户手动选择的光标位置,自动跳过逗号位置
        if (newValue.selection.baseOffset < i) {
          cursorOffset = newValue.selection.baseOffset;
          if (cursorOffset == 2) cursorOffset = 1;
        } else {
          cursorOffset = i;
        }
        break;
      }
    }
    // 输入满额时光标自动定位到末尾
    if (rawDigits.length == _maxInputLength) {
      cursorOffset = displayText.length;
    }

    super.value = TextEditingValue(
      text: displayText,
      selection: TextSelection.collapsed(offset: cursorOffset),
      composing: TextRange.empty,
    );
  }
}

2. 组件接入配置

// 初始化控制器
final amountController = PlaceholderAmountController()..text = '--,--';

TextField(
  controller: amountController,
  keyboardType: TextInputType.number,
  enableInteractiveSelection: true, // 开启光标移动选择能力
  showCursor: true,
  inputFormatters: [
    FilteringTextInputFormatter.allow(RegExp(r'[0-9,-]')),
  ],
  style: const TextStyle(fontSize: 26, letterSpacing: 3),
  decoration: const InputDecoration(
    border: InputBorder.none,
    prefixText: '¥ ', // 可按需配置货币符号对齐PayPal样式
    prefixStyle: TextStyle(fontSize: 26),
  ),
)

交互适配说明

  • 光标移动:控制器自动做了位置校准,用户手动拖动光标到任意可输入位置时,输入数字会直接替换对应位置的占位符,不会出现光标跳位
  • 退格逻辑:无论光标在哪个位置,按退格键只会删除光标左侧最近的一位有效数字,对应位置自动还原为-占位符,符合常规输入习惯
  • 扩展修改:需要支持更长金额输入时,只需修改_template和_maxInputLength即可;需要支持小数的场景,在模板中增加小数点位置、控制器内补充跳过逻辑即可快速适配

内容的提问来源于stack exchange,提问作者Colin Schneider

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 02:28:04