[Flutter] 認証コード入力画面を自作する
背景
業務でSMS認証コード入力画面を実装するタスクがあり、その実装に少し手間取ったので共有します。
pub.devにpin_code_fields等もありましたが、如何せんサードパーティー製はカスタム性に乏しく業務のデザインにフィットしなかったので自作することにしました。
完成形のGIFは以下になります。(DartPadよりGIFの動作を確認できます。)
設計
当初の想定では、TextField
, TextEditingController
, FocusNode
を入力文字数分だけ用意し、FocusNode#requestFocus
, FocusNode#unFocus
を利用することで簡単に実装できるだろうと思っていました。
しかし、実際に実装してみるとわかるのですが、それだけでは文字をクリアするときの挙動を表現することができませんでした。というのも、未入力状態のTextField
に対してクリアを押してもTextField#onChanged
コールバックが発火しないので、FocusNode#requestFocus
で一つ前のTextField
にフォーカスすることができなかったのです。
そこでゼロ幅スペースを各TextField
の1文字目に設定することで、TextField#onChanged
のイベントを発火させるように設計しました。以下の図はイメージです。
実装
設計を元に、TextField
の1文字目にゼロ幅スペースを入れ、適宜TextSelection
で入力時のカーソルを調整します。
以下は、TextField#onChanged
に指定する関数になります。
void _onChanged(String str, int index) {
if (str.length == 1 && str != space) {
for (final controller in controllerList) {
controller.text = space;
}
if (int.tryParse(str) == null) {
return;
}
controllerList[index].value = TextEditingValue(
text: space + str,
selection: const TextSelection.collapsed(offset: 2));
focusNodeList[index + 1].requestFocus();
return;
}
if (str.length == charCount) {
str.split('').asMap().forEach((index, value) {
controllerList[index].value = TextEditingValue(
text: space + value,
selection: const TextSelection.collapsed(offset: 2));
});
focusNodeList[index].unfocus();
return;
}
if (str.length >= 2) {
if (int.tryParse(str.substring(str.length - 1, str.length)) == null) {
controllerList[index].value = TextEditingValue(
text: space, selection: const TextSelection.collapsed(offset: 1));
return;
}
if (str.length > 2) {
final newStr = space + str.substring(str.length - 1, str.length);
controllerList[index].value = TextEditingValue(
text: newStr, selection: const TextSelection.collapsed(offset: 2));
}
if (index < charCount - 1) {
focusNodeList[index + 1].requestFocus();
} else if (index == charCount - 1) {
focusNodeList[index].unfocus();
}
return;
}
if (str.length == 1 && str == space && index != 0) {
focusNodeList[index - 1].requestFocus();
return;
}
if (str.isEmpty) {
controllerList[index].value = TextEditingValue(
text: space, selection: const TextSelection.collapsed(offset: 1));
if (index != 0) {
focusNodeList[index - 1].requestFocus();
}
}
}
参考
StackOverFlowを参考にした覚えがありますが実装したのが半年ほど前で、現在そのページを見つけることができませんでした。見つかり次第、リンクを貼ります。
おわりに
200行程度で自作できたので、カスタム性を考えるとパッケージを利用するよりコスパはいい気がしてます。
もっといい方法があればコメントにて教えていただけると幸いです。
最後まで見ていただき、ありがとうございました。
Discussion