適用場景:手機號、銀行卡號、身份證號、信用卡號、分段輸入格式化
難點:輸入時自動插入空格 + 光標不亂跳 + 刪除也正常
一、為什麼不能只用 onChanged?
很多人第一反應是:
onChanged: (value) {
controller.text = format(value);
}
但這麼做會出現兩個典型問題:
- 光標跳到最後:用户在中間編輯 → 光標突然跑到末尾
- 中文輸入法衝突:中文組合輸入被打斷,甚至輸入不了漢字
正確姿勢:用 inputFormatters,這是框架設計給我們用來格式化輸入的入口。
二、目標需求
以手機號為例:
13800138000 → 138 0013 8000
要求:
- 輸入到第 3/7 位時自動插空格
- 刪除空格時智能回退
- 在中間插入 / 刪除字符時,光標保持正確位置
- 不能破壞中文輸入法
- 支持粘貼內容
三、核心思路
inputFormatters 在每次輸入更新時都會調用:
formatEditUpdate(oldValue, newValue)
我們要做三件事:
- 提取用户輸入的純數字
- 把數字按規則插入空格
- 計算新的光標位置,並返回新的 TextEditingValue
最終返回的值:
TextEditingValue(
text: 格式化後的文本,
selection: 光標位置,
)
四、完整可用的手機號空格 Formatter
import 'package:flutter/services.dart';
/// 手機號輸入格式化:3-4-4 分組
class PhoneNumberFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
// 1. 輸入為空直接返回
if (newValue.text.isEmpty) {
return newValue;
}
// 2. 提取純數字
String digits = newValue.text.replaceAll(RegExp(r'\D'), '');
// 限制最大長度 11 位
if (digits.length > 11) {
digits = digits.substring(0, 11);
}
// 3. 格式化 3-4-4
String formatted = _formatDigits(digits);
// 4. 計算光標位置
int cursorPosition = _calculateCursorPosition(
newValue,
formatted,
);
return TextEditingValue(
text: formatted,
selection: TextSelection.collapsed(offset: cursorPosition),
);
}
/// 按 3-4-4 插入空格
String _formatDigits(String digits) {
final buffer = StringBuffer();
for (int i = 0; i < digits.length; i++) {
buffer.write(digits[i]);
if (i == 2 || i == 6) buffer.write(' ');
}
return buffer.toString();
}
/// 根據新文本和格式化後文本,計算正確光標位置
int _calculateCursorPosition(
TextEditingValue newValue,
String formatted,
) {
int cursor = newValue.selection.end;
// 輸入時,當光標越過第 3 或 8 位置(包含空格),需要往後+1
if (cursor == 4 || cursor == 9) {
cursor++;
}
// 刪除空格時,光標需要回退
if (cursor > formatted.length) {
cursor = formatted.length;
}
return cursor;
}
}
五、使用方式
TextField(
keyboardType: TextInputType.phone,
inputFormatters: [
PhoneNumberFormatter(),
],
decoration: const InputDecoration(
hintText: '請輸入手機號',
border: OutlineInputBorder(),
),
)
六、效果演示
|
操作
|
效果
|
|
輸入 |
自動變成 |
|
刪除空格
|
自動回退,不會卡住
|
|
在中間插入數字
|
光標保持正確位置
|
|
粘貼 13800138000
|
自動格式化
|
|
中文輸入法
|
不受影響
|
七、為什麼光標會亂跳?(原理解析)
如果你只設置 controller.text:
- Flutter 會認為“你重新賦值了文本”
- 框架會將光標重置到文本末尾
所以我們必須同時設置 selection,告訴框架光標應該在哪裏:
selection: TextSelection.collapsed(offset: cursorPosition)
而 cursorPosition 必須經過計算,不是簡單的 text.length。
八、如何擴展為其他格式?
✅ 銀行卡號:4-4-4-4-...
1111 2222 3333 4444
修改 _formatDigits 即可:
if ((i + 1) % 4 == 0 && i != digits.length - 1) {
buffer.write(' ');
}
✅ 身份證號:6-8-4
✅ 自定義分組:[N, N, N, ...]
你可以把分組規則做成參數:
PhoneFormatter(group: [3, 4, 4])
九、常見坑總結
|
坑
|
説明
|
|
寫在 onChanged 裏
|
✅ 光標亂跳,❌ 中文輸入法異常
|
|
沒處理刪除行為
|
刪除空格會卡住
|
|
沒處理光標中間插入
|
光標會跳末尾
|
|
沒處理粘貼
|
粘貼格式錯誤
|
|
直接 return newValue
|
不會格式化,也無法限制長度
|