Flutter 小數點輸入問題:iOS 鍵盤因地區設定顯示逗號的解決方案
目錄
摘要
在 Flutter 中使用 TextInputType.numberWithOptions(decimal: true) 時,iOS 會根據裝置的地區設定顯示不同的小數點分隔符。在一些使用逗號(,)作為小數點的地區,如果你的 inputFormatters 只接受句點(.),使用者將無法輸入小數。本文將說明問題的根本原因並提供一個可重複使用的解決方案。
問題描述
有使用者回報在我們健康追蹤 App 的體重輸入欄位中無法輸入小數值。數字鍵盤雖然出現了,但按下小數點鍵卻沒有任何反應。

查看截圖後,我們發現了一個關鍵點:鍵盤在小數點的位置顯示的是逗號(,)而不是句點(.)。
根本原因分析
iOS 如何處理小數點分隔符
iOS 在顯示數字鍵盤時會尊重裝置的地區設定。在許多地區(如台灣、歐洲大部分地區、南美洲),逗號被用作小數點分隔符:
| 地區 | 小數點分隔符 | 範例 |
|---|---|---|
| 美國、英國、中國 | . (句點) | 65.5 |
| 台灣、德國、法國 | , (逗號) | 65,5 |
當你設定 keyboardType: TextInputType.numberWithOptions(decimal: true) 時,Flutter 會告訴 iOS 顯示一個帶有小數點鍵的數字鍵盤。然而,iOS 會根據地區決定那個鍵輸出什麼字元。
罪魁禍首:FilteringTextInputFormatter
我們最初的實作使用了:
TextFormField(
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*')),
],
// ...
)
這個正規表達式 ^\d*\.?\d* 只接受:
- 數字 (
\d*) - 選擇性的一個句點 (
\.?) - 更多數字 (
\d*)
當台灣的使用者按下小數點鍵時,iOS 發送了一個逗號(,),由於不符合正規表達式的規則,格式化器(formatter)立即拒絕了該輸入。
解決方案
建立支援地區設定的小數點輸入格式化器
我們建立了一個自訂的 TextInputFormatter,它能夠:
- 接受
.和,作為小數點分隔符 - 將逗號標準化為句點,以保持解析的一致性
- 可選擇限制小數位數
import 'package:flutter/services.dart';
/// 一個允許使用 '.' 和 ',' 作為小數點輸入的 TextInputFormatter,
/// 並將 ',' 標準化為 '.' 以保持解析一致性。
///
/// 這處理了 iOS 地區差異,在某些地區數字鍵盤會顯示 ',' 而不是 '.'。
class DecimalTextInputFormatter extends TextInputFormatter {
/// 小數位數限制。如果為 null,則不限制小數位數。
final int? decimalPlaces;
DecimalTextInputFormatter({this.decimalPlaces});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
// 為了地區相容性,將逗號替換為句點
final newText = newValue.text.replaceAll(',', '.');
// 根據小數位數限制建立正規表達式模式
final pattern = decimalPlaces != null
? r'^\d*\.?\d{0,' + decimalPlaces.toString() + r'}$'
: r'^\d*\.?\d*$';
final regex = RegExp(pattern);
if (newText.isEmpty || regex.hasMatch(newText)) {
return TextEditingValue(
text: newText,
selection: newValue.selection,
);
}
return oldValue;
}
}
使用方式
將 FilteringTextInputFormatter 替換為我們的自訂格式化器:
// 修改前
TextFormField(
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*')),
],
)
// 修改後
TextFormField(
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
DecimalTextInputFormatter(),
],
)
對於需要限制小數位數的欄位(例如貨幣需要 2 位小數):
TextFormField(
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
DecimalTextInputFormatter(decimalPlaces: 2),
],
)
測試
遵循 TDD 原則,我們在實作前編寫了完整的測試:
group('DecimalTextInputFormatter', () {
group('逗號轉句點轉換', () {
test('應將逗號轉換為句點', () {
final formatter = DecimalTextInputFormatter();
final result = formatter.formatEditUpdate(
const TextEditingValue(text: '123'),
const TextEditingValue(text: '123,45'),
);
expect(result.text, '123.45');
});
test('應處理開頭為逗號的情況', () {
final formatter = DecimalTextInputFormatter();
final result = formatter.formatEditUpdate(
const TextEditingValue(text: ''),
const TextEditingValue(text: ',5'),
);
expect(result.text, '.5');
});
});
group('無效輸入拒絕', () {
test('應拒絕多個小數點', () {
final formatter = DecimalTextInputFormatter();
final result = formatter.formatEditUpdate(
const TextEditingValue(text: '12.3'),
const TextEditingValue(text: '12.3.4'),
);
expect(result.text, '12.3'); // 保持不變
});
});
group('小數位數限制', () {
test('應拒絕超過小數位數限制的輸入', () {
final formatter = DecimalTextInputFormatter(decimalPlaces: 2);
final result = formatter.formatEditUpdate(
const TextEditingValue(text: '12.34'),
const TextEditingValue(text: '12.345'),
);
expect(result.text, '12.34'); // 保持不變
});
});
});
重點總結
1. 永遠考慮國際化
即使是像數字輸入這樣簡單的功能,在不同地區也可能有不同的行為。請務必使用不同的地區設定測試您的 App。
2. 不要只依賴鍵盤類型
TextInputType.numberWithOptions(decimal: true) 只是建議顯示什麼樣的鍵盤。它並不保證輸入的會是什麼字元。
3. 儘早標準化輸入
透過在輸入階段轉換特定地區的字元,後續的程式碼(驗證、解析、儲存)就不需要處理多種格式。
4. 測試邊界情況
我們的測試套件涵蓋了:
- 空輸入
- 開頭小數點 (
.5) - 結尾小數點 (
123.) - 多個小數點分隔符
- 混合逗號和句點輸入
- 小數位數限制
受影響的平台
| 平台 | 受影響 | 原因 |
|---|---|---|
| iOS | 是 | 小數點分隔符會根據裝置地區設定而變 |
| Android | 部分 | 有些鍵盤會尊重地區設定,有些則不會 |
| Web | 是 | 瀏覽器地區設定會影響鍵盤行為 |
結論
這個這看似簡單的 Bug 教了我們重要的一課:永遠要使用不同的地區設定進行測試。在開發環境(通常是使用句點作為小數點的 美國/英國 地區)運作完美的程式,在其他地區的使用者手中可能會完全壞掉。
一旦了解了根本原因,修復方法就很直觀。透過建立可重複使用的 DecimalTextInputFormatter,我們現在有一個適用於所有地區的穩健解決方案,同時保持資料的乾淨和可解析性。


