1. kazuhideoki

    Posted

    kazuhideoki
Changes in title
+【Flutter】TextFormFieldで入力値をフォーマットする
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,113 @@
+## 要約
+
+- `inputFormatters`に`TextInputFormatter`を継承したCustomFormatterをセット
+- `validator` `maxLength` `buildCounter`にはフォーマット後の値を反映させること(できればもっと楽にしたい)
+
+![expiry_date.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558894/2e1f8a60-6222-8bb2-7637-21ff2a71bb43.gif)
+
+
+## きっかけ
+クレジットカードの入力フォームを作ろうとしたときに入力フォームを工夫したかった。具体的には、カードナンバーで4桁ごとに空白を入れること、有効期限で`/`(スラッシュ)を入れること。
+
+## `TextInputFormatter`を継承したCustomTextInputFormatterの作成
+
+`formatEditUpdate`にて `oldValue`と`newValue`が渡ってくるのでこの2つを比較して処理を決定します。
+入力時だけでなく、削除時の実装も忘れずに。
+
+これを`TextFormField`の`inputFormatters`に格納します。
+
+```dart
+class CustomTextInputFormatter extends TextInputFormatter {
+ TextEditingValue formatEditUpdate(
+ TextEditingValue oldValue, TextEditingValue newValue) {
+ if ('フォーマットを変更する時') {
+ final newText = 'hoge'; // フォーマット後の文字
+ return newValue.copyWith(
+ text: newText,
+ // カーソルの位置を端へ
+ selection: TextSelection.collapsed(offset: newText.length),
+ );
+ }
+
+ // フォーマットしない場合はそのままnewValueを返す
+ return newValue;
+ }
+}
+```
+
+## バリデーションや文字数カウントへの対応
+
+フォーマットのやり方によって文字数が変わってしまうので、バリデーションや文字数のカウントにも対応する必要があります。
+
+クレジットカードの有効期限だと
+
+`04/23`
+
+打ち込むのは4文字分、でも表示は5文字分。
+
+具体的には`validator` `maxLength` `buildCounter`に反映させます。
+
+※これをもうちょっとオシャレに、楽にやる方法知ってる方はご教授くださると嬉しいです。
+
+有効期限のフォームのサンプルです。
+
+## サンプルコード
+```dart
+class ExpiryDateTextField extends StatelessWidget {
+ ExpiryDateTextField();
+
+ @override
+ Widget build(BuildContext context) {
+ return TextFormField(
+ // '/'を含めた5文字
+ maxLength: 5,
+ buildCounter: (context,
+ {required int currentLength,
+ required bool isFocused,
+ required int? maxLength}) {
+ // `/`を抜いた文字数でカウント
+ final length = currentLength < 3 ? currentLength : currentLength - 1;
+ return CustomText.caption('$length/4');
+ },
+ // 配列の中に格納
+ inputFormatters: [_ExpiryDateInputFormatter()],
+ validator: (String? value) {
+ if (value?.length == 0) return '有効期限が入力されていません';
+ // '/'を含めた5文字
+ if (value?.length != 5) return '有効期限が不正です';
+ },
+ );
+ }
+}
+
+class CustomTextInputFormatter extends TextInputFormatter {
+ TextEditingValue formatEditUpdate(
+ TextEditingValue oldValue, TextEditingValue newValue) {
+
+ // 入力している時
+ if (oldValue.text.length == 2 && newValue.text.length == 3) {
+ final newText = '${oldValue.text}/${newValue.text.substring(2)}';
+ return newValue.copyWith(
+ text: newText,
+ selection: TextSelection.collapsed(offset: newText.length),
+ );
+ }
+
+ // 削除している時
+ if (oldValue.text.length == 4 && newValue.text.length == 3) {
+ final newText = newValue.copyWith().text.substring(0, 2);
+ return newValue.copyWith(
+ text: newText,
+ selection: TextSelection.collapsed(offset: newText.length),
+ );
+ }
+
+ return newValue;
+ }
+}
+
+```
+
+## 感想
+
+カスタムフォーマッターは使い回せるので便利だ