Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

posted at

updated at

【Flutter】TextFormFieldで入力値をフォーマットする

要約

  • inputFormattersTextInputFormatterを継承したCustomFormatterをセット
  • validator maxLength buildCounterにはフォーマット後の値を反映させること(できればもっと楽にしたい)

expiry_date.gif

きっかけ

クレジットカードの入力フォームを作ろうとしたときに入力フォームを工夫したかった。具体的には、カードナンバーで4桁ごとに空白を入れること、有効期限で/(スラッシュ)を入れること。

TextInputFormatterを継承したCustomTextInputFormatterの作成

formatEditUpdateにて oldValuenewValueが渡ってくるのでこの2つを比較して処理を決定します。
入力時だけでなく、削除時の実装も忘れずに。

これをTextFormFieldinputFormattersに格納します。

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に反映させます。

※これをもうちょっとオシャレに、楽にやる方法知ってる方はご教授くださると嬉しいです。

有効期限のフォームのサンプルです。

サンプルコード

class ExpiryDateTextField extends StatelessWidget {
  ExpiryDateTextField();

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      // '/'を含めた5文字
      maxLength: 5,
      buildCounter: (context,
          {int currentLength,
          bool isFocused,
          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;
  }
}

感想

カスタムフォーマッターは使い回せるので便利だ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?