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
相关产品推荐
相关产品推荐

