対象者
- ユーザー体験を向上させるための適切なフォーム設計を学びたい方
- TextFormFieldのベストプラクティスを理解し、適用したい方
- Flutterアプリのコンバージョン率を向上させたい方
はじめに
Flutterアプリのフォーム入力は、ユーザー体験やコンバージョン率に大きな影響を与える重要な要素です。しかし、TextFormFieldの実装でよくあるミスが原因で、ユーザーが入力しにくいUIになってしまうことがあります。この記事では、FlutterのTextFormFieldを適切に実装するためのポイントを解説していきます。それぞれのフォーム実装でのベストプラクティスを見つけるための一助になると幸いです
textInputActionを設定する
デフォルトでは、TextFormFieldの入力が完了するとキーボードの「完了」ボタンを押すとキーボードが閉じます。しかし、ユーザーにとっては次のフィールドへ自動でフォーカスが移るほうが便利です。これを実現するには、textInputAction
を適切に設定します。
TextFormField(
decoration: const InputDecoration(
labelText: 'フィールド1',
),
textInputAction: TextInputAction.next, // 次のフィールドへフォーカス移動
),
次のフィールドへフォーカスを移動するには、全てのTextFormFieldに textInputAction: TextInputAction.next
を設定しましょう。
主なtextInputActionの種類
done
: キーボードを閉じるgo
: フォームを送信するsearch
: 検索を実行するsend
: メッセージを送信するprevious
: 前のフィールドに戻る
textInputActionの詳細
-
done
: キーボードを閉じる。フォーム入力完了を意味し、最終的な処理を実行する想定。- Android: チェックマークなどの完了ボタン
- iOS: "Done" と表示
-
go
: 入力されたテキストに基づき、特定の画面へ遷移する。- Android: 右向き矢印ボタン
- iOS: "Go" と表示
-
search
: 検索を実行する。- Android: 虫眼鏡アイコン
- iOS: "Search" と表示
-
send
: 入力内容を送信する(例:メールやメッセージ)。- Android: 紙飛行機のようなアイコン
- iOS: "Send" と表示
-
next
: 次の入力フィールドへフォーカスを移す。- Android: 右向き矢印ボタン
- iOS: "Next" と表示
-
previous
: 前の入力フィールドに戻る。- Android: 左向き矢印ボタン
- iOS: 非対応(使用非推奨)
-
newline
: 改行を挿入し、フォーカスを外さない。- Android: 改行キー(IME_ACTION_NONE)
- iOS: "return" と表示(UIReturnKeyDefault)
-
none
: 特に実行すべきアクションがないことを示す。- Android: IME_ACTION_NONE、主に改行キー
- iOS: 非対応(使用非推奨)
-
unspecified
: OSに適切なアクションの選択を任せる。- Android: IME_ACTION_UNSPECIFIED
- iOS: "return"(UIReturnKeyDefault)
iOSのみでサポートされているアクション
-
continueAction
: フォーム入力時に画面上部の「続ける」ボタンが隠れている状況を補う目的で使用。- iOS: "Continue"(iOS 9.0+ 対応)
- Android: 非対応
-
emergencyCall
: 緊急通報用。- iOS: "Emergency Call"
- Android: 非対応
-
route
: ナビゲーションルートを選択する。- iOS: "Route"
- Android: 非対応
-
join
: 何かに参加する(例:Wi-Fiネットワーク)。- iOS: "Join"
- Android: 非対応
iOSのみでサポートされているアクション
continueAction
: 次のステップに進むアクションemergencyCall
: 緊急通報用のアクションroute
: ルート情報を入力するアクションjoin
: 参加を示すアクション
Androidのみでサポートされているアクション
none
: キーボードが特定のアクションを実行しないことを示す。この設定では、キーボードによっては改行がデフォルトアクションとなるが、IMEアクションのコールバックには送信されない。
submitアクションを適切に設定する
最後の入力フィールドでは、キーボードの「完了」ボタンを押した際に何かアクションを実行することが望ましいです。onFieldSubmitted
を使うと、ボタンを押したタイミングで処理を実行できます。
TextFormField(
decoration: const InputDecoration(
labelText: 'フィールド3',
),
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) {
startLoading(); // 例:データ送信処理を開始
},
),
正しいキーボードタイプを使用する
フォーム入力の種類によって適切なキーボードタイプを設定することで、ユーザーの入力をスムーズにできます。
TextFormField(
keyboardType: TextInputType.emailAddress, // メール入力用キーボード
decoration: const InputDecoration(
labelText: 'Email',
),
)
主なキーボードタイプ
TextInputType.phone
: 電話番号入力用TextInputType.emailAddress
: メールアドレス入力用TextInputType.number
: 数字のみ入力可能TextInputType.url
: URL入力用TextInputType.datetime
: 日付と時間の入力に最適化TextInputType.multiline
: 複数行のテキスト入力に最適化TextInputType.name
: 人名入力に最適化TextInputType.none
: OSがオンスクリーンキーボードを表示しないようにするTextInputType.number
: 小数点なしの数値入力に最適化TextInputType.phone
: 電話番号入力用TextInputType.streetAddress
: 郵送先住所の入力に最適化TextInputType.text
: テキスト情報の入力に最適化TextInputType.twitter
: ソーシャルメディア用に最適化TextInputType.url
: URL入力用TextInputType.visiblePassword
: ユーザーが視認できるパスワード入力に最適化TextInputType.webSearch
: Web検索の入力に最適化
4. TextCapitalizationを活用する
名前やタイトルなど、一部のフィールドでは自動で大文字に変換するのが望ましいです。textCapitalization
を使うと、入力の自動変換が可能です。
TextFormField(
textCapitalization: TextCapitalization.words, // 単語ごとに大文字
decoration: const InputDecoration(
labelText: '名前',
),
)
主な設定
none
: そのままの文字列を入力characters
: すべて大文字words
: 各単語の先頭を大文字sentences
: 文の先頭を大文字
TextInputFormatterで入力制限を行う
フォーム入力時に不適切な文字を防ぐためには、TextInputFormatter
を活用します。
TextFormField(
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'[^a-zA-Z0-9]'))],
decoration: const InputDecoration(
labelText: 'ユーザー名',
),
)
この例では、英数字以外の文字を入力不可にしています。
定義済みフォーマッター
TextInputFormatter.digitsOnly
- 数字(0-9)のみを許可するフォーマッター。
TextInputFormatter.singleLineFormatter
- 入力を1行のみに制限するフォーマッター。
以下のようなカスタム例を作成してます
https://flutter.salon/widget/textinputformatter/
AutofillHintsを利用する
ユーザーの手間を減らすため、autofillHints
を利用して自動入力のヒントを提供します。こちらの項目は、結構ややこしそうなので、別記事を作ろう、、、
TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
),
autofillHints: const [AutofillHints.email],
keyboardType: TextInputType.emailAddress,
)
使用できる主な autofillHints
の種類(一部抜粋)
AutofillHints.email
:メールアドレスAutofillHints.name
:氏名AutofillHints.username
:ユーザー名AutofillHints.password
:パスワードAutofillHints.newPassword
:新しいパスワードAutofillHints.postalAddress
:住所AutofillHints.telephoneNumber
:電話番号AutofillHints.creditCardNumber
:クレジットカード番号AutofillHints.birthdayDay
,AutofillHints.birthdayMonth
,AutofillHints.birthdayYear
:誕生日関連AutofillHints.gender
:性別AutofillHints.countryCode
:国コード
AutofillGroup
を利用する
AutofillGroup
を利用すると、フォーム全体の入力を統一的に管理でき、複数フィールドの一括保存や自動補完が有効になります。
Q&A
Q1: textInputAction
を適切に設定しないと何が問題ですか?
A1: textInputAction
を設定しないと、キーボードの「完了」ボタンを押した際に次のフィールドへ移動できず、ユーザーが手動でフォーカスを変更しなければならないため、入力体験が悪化します。
Q2: keyboardType
はどれを選べば良いですか?
A2: 入力内容に応じて設定しましょう。例えばメールアドレスには TextInputType.emailAddress
、電話番号には TextInputType.phone
を使うことで、ユーザーが使いやすいキーボードが表示されます。
Q3: TextInputFormatter
はバリデーションと何が違いますか?
A3: TextInputFormatter
は入力中の文字を制限するものです。バリデーションは入力後にエラー表示するのに対し、Formatterは入力そのものを制御できるため、よりユーザーフレンドリーな操作を提供できます。
まとめ
この記事では、FlutterのTextFormField
を正しく、そして使いやすく設計するための実践的なポイントを解説しました。特に以下の点が重要です:
textInputAction
を使って快適なフォーカス移動を実現する- 入力内容に応じた
keyboardType
を設定する TextCapitalization
やTextInputFormatter
を活用し、より自然な入力体験を提供する
Flutterのフォーム設計は、細部への気配りがユーザー体験を大きく左右します。今回紹介したポイントを押さえることで、使いやすく、信頼性の高いフォームを実装できるようになります。
参考
-
autofillHints property
自動入力を詳しく書かれている。実際に使用したブログ記事を作成したい
ソース(main.dartにコピペして動作確認用)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('TextFormField Best Practices')),
body: const MyForm(),
),
);
}
}
class MyForm extends StatelessWidget {
const MyForm({super.key});
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: [
Form(
child: Column(
children: [
TextFormField(
minLines: 3,
maxLines: 3,
decoration: const InputDecoration(labelText: '複数行'),
textInputAction: TextInputAction.newline,
),
TextFormField(
decoration: const InputDecoration(labelText: '押すと戻る'),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.previous,
),
TextFormField(
decoration: const InputDecoration(labelText: '電話番号'),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next,
),
TextFormField(
decoration: const InputDecoration(labelText: '名前'),
textCapitalization: TextCapitalization.words,
textInputAction: TextInputAction.next,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
autofillHints: const [AutofillHints.email],
),
TextFormField(
decoration: const InputDecoration(labelText: 'パスワード'),
obscureText: true,
textInputAction: TextInputAction.done,
autofillHints: const [AutofillHints.password],
),
],
),
),
Divider(),
Text('AutofillGroupを使用'),
Form(
child: AutofillGroup(
child: Column(
children: [
TextFormField(
autofillHints: const [AutofillHints.name],
keyboardType: TextInputType.name,
decoration: const InputDecoration(labelText: 'Name'),
),
TextFormField(
autofillHints: const [AutofillHints.email],
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(labelText: 'Email'),
),
TextFormField(
autofillHints: const [AutofillHints.password],
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
),
],
),
),
)
],
),
),
);
}
}