基于自定义货币配置的Flutter TextFormField数字格式化(无需Locale)
问题:基于自定义货币配置实现TextFormField数字格式化
我有一份包含各类货币配置的currencies列表(JSON格式):
[ { "code": "USD", "name": "United States Dollar", "symbol": "$", "flag": "USD", "decimal_digits": 2, "number": 840, "name_plural": "US dollars", "thousands_separator": ",", "decimal_separator": ".", "space_between_amount_and_symbol": false, "symbol_on_left": true }, { "code": "EUR", "name": "Euro", "symbol": "€", "flag": "EUR", "decimal_digits": 2, "number": 978, "name_plural": "Euros", "thousands_separator": ".", "decimal_separator": ",", "space_between_amount_and_symbol": true, "symbol_on_left": false }, ... ]
目前我已经实现了将double类型转换为格式化价格的函数,但核心需求是:基于指定currencyCode实现TextFormField的实时文本格式化,不显示货币符号,仅展示符合对应货币规则的数字。
试过多种方案及第三方包,但大多依赖locale或无法适配所有场景。请问现有货币配置信息是否足够?如何实现该需求?
示例(USD):
- 输入:400 → 预期输入框文本:
4,00 - 输入:696942 → 预期输入框文本:
6,969.42
解答
现有配置是否足够?
完全足够。实现需求只需要用到配置里的三个核心字段:
decimal_digits:小数位数,控制输入的小数部分长度thousands_separator:千位分隔符,用于整数部分的分组decimal_separator:小数点分隔符,区分整数和小数部分
其他字段(如symbol、name等)暂时用不上。
实现步骤(Flutter/Dart场景)
1. 定义货币配置模型类
先把JSON配置转成强类型模型,方便后续操作:
class CurrencyConfig { final String code; final int decimalDigits; final String thousandsSeparator; final String decimalSeparator; CurrencyConfig({ required this.code, required this.decimalDigits, required this.thousandsSeparator, required this.decimalSeparator, }); factory CurrencyConfig.fromJson(Map<String, dynamic> json) { return CurrencyConfig( code: json['code'], decimalDigits: json['decimal_digits'], thousandsSeparator: json['thousands_separator'], decimalSeparator: json['decimal_separator'], ); } }
2. 实现货币配置查询函数
根据指定的currencyCode从配置列表中获取对应的规则:
CurrencyConfig? getCurrencyConfig(String currencyCode, List<Map<String, dynamic>> currencies) { try { final currencyJson = currencies.firstWhere((c) => c['code'] == currencyCode); return CurrencyConfig.fromJson(currencyJson); } catch (_) { return null; } }
3. 自定义TextInputFormatter实现实时格式化
核心是编写自定义输入格式化器,处理用户输入的实时转换:
class CurrencyNumberFormatter extends TextInputFormatter { final CurrencyConfig config; CurrencyNumberFormatter(this.config); @override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { // 过滤掉所有非数字字符 final cleanText = newValue.text.replaceAll(RegExp(r'[^0-9]'), ''); // 拆分整数和小数部分 String integerPart = cleanText; String decimalPart = ''; if (config.decimalDigits > 0) { if (cleanText.length > config.decimalDigits) { integerPart = cleanText.substring(0, cleanText.length - config.decimalDigits); decimalPart = cleanText.substring(cleanText.length - config.decimalDigits); } else { // 输入长度不足小数位数时,补0并将整数部分设为0 decimalPart = cleanText.padLeft(config.decimalDigits, '0'); integerPart = '0'; } } // 给整数部分添加千位分隔符 final formattedInteger = _addThousandsSeparator(integerPart, config.thousandsSeparator); // 拼接最终格式化文本 String formattedText = formattedInteger; if (config.decimalDigits > 0 && decimalPart.isNotEmpty) { formattedText += '${config.decimalSeparator}$decimalPart'; } // 计算光标位置,保证输入时光标不跳转 int cursorPosition = formattedText.length; if (newValue.selection.end > 0) { final originalCursorIndex = newValue.selection.end; final cleanCursorIndex = cleanText.substring(0, originalCursorIndex).length; cursorPosition = _calculateCursorPosition(formattedText, cleanCursorIndex, config); } return TextEditingValue( text: formattedText, selection: TextSelection.collapsed(offset: cursorPosition), ); } // 给数字添加千位分隔符 String _addThousandsSeparator(String number, String separator) { if (number.isEmpty) return ''; final reversed = number.split('').reversed.join(); final parts = RegExp(r'(.{1,3})').allMatches(reversed).map((m) => m.group(0)).join(separator); return parts.split('').reversed.join(); } // 计算格式化后的光标位置 int _calculateCursorPosition(String formattedText, int cleanCursorIndex, CurrencyConfig config) { String integerPart = formattedText.split(config.decimalSeparator).first; // 统计千位分隔符的数量 int separatorCount = (integerPart.length - 1) ~/ 3; int cursorPos = cleanCursorIndex + separatorCount; // 如果光标在小数部分,加上小数点的位置 if (cleanCursorIndex > integerPart.replaceAll(config.thousandsSeparator, '').length) { cursorPos += 1; } return cursorPos.clamp(0, formattedText.length); } }
4. 在TextFormField中使用
将自定义格式化器绑定到输入框:
// 假设已有currencies配置列表和选中的currencyCode final currencyConfig = getCurrencyConfig('USD', currencies); TextFormField( inputFormatters: currencyConfig != null ? [CurrencyNumberFormatter(currencyConfig)] : [], keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: '金额', ), )
测试验证
- 输入
400时,会自动格式化为4,00(符合USD规则) - 输入
696942时,格式化为6,969.42 - 切换为EUR时,输入
696942会格式化为6.969,42,符合欧元的数字规则
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

