ユーザーに何か情報を入力してもらい、保存ボタンを押したときにHUDを表示するのではなく、画面のインタラクションを制御しつつ、保存ボタンに進捗を表示して表現したいなと思いProgressButtonなるものを自作してみました。
仕様
汎用的、というよりは自分が今開発しているアプリで必要な機能に絞りつつ、以下のような仕様で実装することにしました。
そこまで難しい状態管理やロジックもありません。
- StatelessWidgetで作成する
- ボタンの状態はButton内で管理はせず、親WidgetからProvider等を活用して受け渡ししてもらう
- ユーザーが情報を入力し、それが有効なデータだった場合にボタンを有効化したいため、
enable
の切り替えができるようにする - ユーザーがボタンを押し、何かしらの処理が実行開始したら
processing
をtrueにし、ボタンにインジケーターを表示する - 処理が完了したら
processing
をfalseにし、ボタンを元の状態に戻せるようにする enable=false
またはprocessing=true
のときはボタンが押せないようにするprocessing=true
の時はボタンの背景色を少し暗めにする- ボタンのタイトルは常に中央揃えにし、インジケーターを表示しているとき/していない時でラベルの位置がずれないように工夫する
コード
丁寧に変数、定数定義したりWidgetの分割をしたため100行弱になりましたが、1つのbuild関数に収めると50行ほどのコード量で収まると思います。
詳細説明
RaisedButton
Widgetをベースに作成しています。ボタンの両端を丸くするためにStadiumBorder
をshapeに指定しています。
ボタンのサイズは高さは固定値、幅はdouble.infinity
を指定して親要素に合わせて拡がれる限り拡がるようにしています。
処理実行中をCircularProgressIndicator
Widetを使って表し、実行中でないときは、それを包んでいるVisibility
Widgetのvisibleの切り替えによって非表示にしています。
※実際の開発で使っているProgressButtonの色合いはlight/darkモードによって色が変わるような処理を入れているため、例ではColors.blue
などの色を代わりに使用しています。
完成品
また、DartPadにもデモをあげてみたのでそちらでも挙動が確認できます。 デモではボタンを押すとボタンが切り替わり、2秒後に元に戻るようになっています。
工夫した点など
SizedBoxの活用
CircularProgressIndicator
を任意のサイズにするためにSizedBox
を使っています。他にも要素間のスペース調整や、ボタン自体のサイズを決める際にもSizedBox
を使っています。便利。
要素のAlignment
また、インジケーターが表示されても非表示になってもラベルは常に中央にするために、Row
Widgetを使い、
- インジケーター(A)
- 要素間のスペース(B)
- ラベル
- (AとBの横幅を足したサイズのSizedBox)
とchildrenを挿入し、axisをcenterにすることで実現しています。
disable時のタップ抑制と色の切り替え
また、processing || !enabled
な時はonPress
にnull
を渡してボタンの状態をdisabledに変更するようにしてタッチを無効化しつつ、ボタンの色がdisabledColor
に切り替わるようにしました。
disabledColor
は、processing時は通常時のbackgroundColor
を半透明にしたもの、そうでなければグレーの色合いを指定しています。
参考にしたもの
今回お見せしたProgressButton、少し前まではprocessing
中は文字を消してインジケーターのみを表示していたのですが、以下の記事を見てそれはあまりよろしくないということで「インジケーター+ラベル」の形かつ、ラベルは常に中央揃えで表示できるように改修しました。