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

Flutter TextField实现实时货币格式化的方法咨询

没问题!在Flutter里实现TextField的实时货币格式化完全可行,我平时做电商类App经常用到这个功能。核心是通过文本输入拦截和控制器监听来同步更新格式化后的内容,还要注意处理光标位置避免体验问题。下面给你两种实用的实现方式:

方法一:自定义TextInputFormatter(完全自定义规则)

如果需要严格控制格式化逻辑(比如指定千分位分隔符、货币符号位置),可以自己写一个TextInputFormatter来拦截并处理输入内容:

class CurrencyInputFormatter extends TextInputFormatter {
  final String currencySymbol;
  final int decimalDigits;

  CurrencyInputFormatter({
    this.currencySymbol = '¥',
    this.decimalDigits = 2,
  });

  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    // 先清除所有非数字和小数点的干扰字符
    String newText = newValue.text.replaceAll(RegExp(r'[^0-9.]'), '');
    
    // 处理用户输入多个小数点的情况
    if (newText.split('.').length > 2) {
      newText = oldValue.text;
    }

    // 拆分整数和小数部分
    List<String> parts = newText.split('.');
    String integerPart = parts[0];
    String decimalPart = parts.length > 1 ? parts[1] : '';

    // 限制小数位数不超过设定值
    if (decimalPart.length > decimalDigits) {
      decimalPart = decimalPart.substring(0, decimalDigits);
    }

    // 给整数部分添加千分位分隔符
    String formattedInteger = _addThousandSeparator(integerPart);

    // 拼接最终的格式化文本
    String formattedText = currencySymbol;
    if (integerPart.isNotEmpty) formattedText += formattedInteger;
    if (decimalPart.isNotEmpty) formattedText += '.$decimalPart';

    // 计算光标位置,避免格式化后光标错位
    int cursorPosition = formattedText.length;
    if (newValue.selection.baseOffset > 0) {
      int originalOffset = newValue.selection.baseOffset;
      int oldNonDigitCount = oldValue.text.replaceAll(RegExp(r'[0-9.]'), '').length;
      int newNonDigitCount = formattedText.replaceAll(RegExp(r'[0-9.]'), '').length;
      cursorPosition = originalOffset + (newNonDigitCount - oldNonDigitCount);
      cursorPosition = cursorPosition.clamp(0, formattedText.length);
    }

    return TextEditingValue(
      text: formattedText,
      selection: TextSelection.collapsed(offset: cursorPosition),
    );
  }

  // 辅助方法:给数字字符串添加千分位分隔符
  String _addThousandSeparator(String text) {
    if (text.isEmpty) return '';
    String reversedText = text.split('').reversed.join();
    String formatted = reversedText.replaceAllMapped(
      RegExp(r'(\d{3})(?=\d)'),
      (match) => '${match.group(1)},',
    );
    return formatted.split('').reversed.join();
  }
}

在TextField中使用这个Formatter:

TextField(
  keyboardType: TextInputType.numberWithOptions(decimal: true),
  inputFormatters: [
    CurrencyInputFormatter(currencySymbol: '¥', decimalDigits: 2),
  ],
  decoration: InputDecoration(
    labelText: '请输入金额',
    border: OutlineInputBorder(),
  ),
)
方法二:用intl包快速实现(省心首选)

如果你不想自己造轮子,Flutter官方推荐的intl包提供了现成的货币格式化工具,能快速适配不同地区的货币格式:

首先在pubspec.yaml中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  intl: ^0.18.1 # 建议使用最新版本

然后自定义一个带格式化逻辑的TextEditingController:

import 'package:intl/intl.dart';

class CurrencyTextEditingController extends TextEditingController {
  final NumberFormat currencyFormat;
  final String currencySymbol;

  CurrencyTextEditingController({
    String? initialText,
    String locale = 'zh_CN',
    this.currencySymbol = '¥',
  }) : currencyFormat = NumberFormat.currency(
          locale: locale,
          symbol: currencySymbol,
          decimalDigits: 2,
        ) {
    if (initialText != null) text = initialText;
  }

  @override
  set text(String newText) {
    // 清除格式化符号,只保留纯数字和小数点
    String cleanText = newText.replaceAll(currencySymbol, '').replaceAll(',', '');
    if (cleanText.isEmpty) {
      super.text = '';
      return;
    }
    // 转换为数字后重新格式化
    double? number = double.tryParse(cleanText);
    if (number != null) {
      super.text = currencyFormat.format(number);
    } else {
      super.text = newText;
    }
    // 光标始终定位到文本末尾
    selection = TextSelection.collapsed(offset: text.length);
  }

  @override
  void addListener() {
    super.addListener();
    // 监听文本变化,实时更新格式化
    final currentText = this.text;
    String cleanText = currentText.replaceAll(currencySymbol, '').replaceAll(',', '');
    if (cleanText.isEmpty) return;
    double? number = double.tryParse(cleanText);
    if (number != null) {
      String formattedText = currencyFormat.format(number);
      if (formattedText != currentText) {
        // 避免循环更新
        value = value.copyWith(
          text: formattedText,
          selection: TextSelection.collapsed(offset: formattedText.length),
        );
      }
    }
  }
}

在TextField中使用这个控制器:

TextField(
  controller: CurrencyTextEditingController(locale: 'zh_CN', currencySymbol: '¥'),
  keyboardType: TextInputType.numberWithOptions(decimal: true),
  decoration: InputDecoration(
    labelText: '请输入金额',
    border: OutlineInputBorder(),
  ),
)

一些注意事项

  • 光标位置:自定义Formatter时一定要处理光标位置,不然用户输入时光标会跳来跳去,严重影响体验,上面的代码已经做了基础适配,你可以根据需求调整。
  • 输入限制:可以配合FilteringTextInputFormatter.digitsOnly进一步限制输入字符类型,但要注意保留小数点的输入权限。
  • 本地化支持:方法二中的locale参数可以设置为en_US(美元)、ja_JP(日元)等,自动适配对应地区的货币格式。

内容的提问来源于stack exchange,提问作者Jorge Vieira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:16:57