Flutter-ダイアログのアニメーション

ダイアログの表示をする際に、ダイアログ自身を拡大・縮小させたり、回転させたりするにはshowGeneralDialog経由transitionBuilderで行うのが一般的にな方法なのだけど、これでは対応できないアニメーションがあったので、その対応方法を調べてみた。

出来なかったのは以下の物。

  1. 位置指定でのダイアログ表示
  2. ダイアログの位置指定でのtransition

位置指定でのダイアログ表示

これは主にあるボタンの下にドロップダウンの様に表示したい場合はどのようにするかという問題。

Flutter-ドロップダウンボタン様式のFilterChipの実装で使ったように、レンダーオブジェクトからウィジェットの位置関係を求めるという方法がある。

ただちょっと調べたら、以下の様なパッケージがあったので、そちらの方が簡単に利用できることが分かった。

aligned_dialog | Flutter package
Show a locally/globally aligned dialog with custom transtion animation.

これを使ううえでの注意点は以下の2点

ターゲットするウィジェットについて

ターゲットとするウィジェットはBuilder等でラッピングしておく必要があるようだ。

          Builder(builder: (context) {
            return IconButton(
                onPressed: () {
                  // 位置はBuilderベースにしているのかな。
                  // パッケージを使用した、対象ウィジェットベースのダイアログ表示位置
                  // が指定できるもの。
                  showAlignedDialog(
                      context: context,
                      builder: _widget,
                      followerAnchor: Alignment.topLeft,
                      targetAnchor: Alignment.bottomLeft,
                      transitionsBuilder: _transition);
                },
                icon: const Icon(Icons.menu));
          }),

showAlignedDialog内でcontext経由で直近のウィジェットの大きさを求めているようなので。(たぶん)

builderで作るウィジェットにDialogはほぼ使えない

DialogウィジェットはPadding→Align→ConstrainedBox→Materialといったウィジェット構成で作成されており、Alignの指定がルート画面が基準になっているので、showAlignedDialogで位置が確定しても、勝手に位置変更されてしまう。


こんな感じに明後日の方向に表示される。

そこで、Dialog相当の構成になるようなウィジェットを作る必要があるのだけど、基本的にDialogの代わりにMaterialを指定すればいい。
この場合Dialogで指定しているエッジのラウンド処理を自前で行う必要があるのが手間かもしれない。

DialogをMaterialに変更した結果が以下の通り。

ボタンの下に表示するようにしたのがそのまま反映される。

ちょっとした実装上のテクニック

ダイアログ様式にするためのコーナーラウンド

Dialogの実装を見てみると、コーナーのラウンド処理は基本ハードコーディングされているのが使われていた。
DialogThemeにshapeというのがあるのだけど、コードを見たらnullでデフォルトは未設定のようだった。

そこで使われるのがDialog内のハードコードされた情報。

Material2では「4.0半径のR」、Material3では「28.0半径のR」となっていたので、自前で作る場合はそれを参考にするといいかもしれない。

縦横の調整

単純なのであればいいのだけど、Columnとかを使うと大きさがちょっとな感じになる。

例えば、Columnの中にListTileを3つ配置したのが個の様に表示される。

下と右がパーンと伸びきってしまっている。

                        child: IntrinsicWidth(
                          child: Column(
                            mainAxisSize: MainAxisSize.min,

上記の様にすれば、問題なくなる。

ダイアログの位置指定でのtransition

次に問題だなと思ったのがtransition関連。

例えば以下の様なコードの場合。

              showGeneralDialog(
                context: context,
                pageBuilder: (context, animation1, animation2) => const Dialog(
                  child: SizedBox(
                      width: 100,
                      height: 100,
                      child: Center(child: Text('Alert!'))),
                ),
                transitionBuilder:
                    (context, animation, secondaryAnimation, child) {
                  debugPrint("trans");
                  return RotationTransition(
                    turns: animation,
                    child: child,
                  );
                },

これを実行すると、以下の様にダイアログが回転しながら表示される。

ただ、ダイアログの左上を中心に回転させたいという要求には答えられない。

例えばRotationTransitionのalignmentをAlignment.topLeftにすると以下の様になる。


わかりやすいようにdurationを5秒にしているのだけど、回転の中心が画面右上になっていて、ダイアログ右上になっていない。

これはダイアログの座標系がルート画面にあるからで、Transitionの回転中心位置を計算してあげて渡せばいいのだけど、上位側から図るのはちょっと実装が難しい。

いろいろ試した結果次のような実装方法に落ち着いた。

  1. ダイアログ表示はaligned_dialogパッケージを使う。
  2. transitionBuilderでTransitionを行うのではなく表示用のダイアログの中にTransitionを配置しておき、それにtransitionBuilderで渡されたanimationを渡す。

それを実装したのが以下のものになる。

位置指定と配置したダイアログ基準としたTransitionアニメーションのサンプル
位置指定と配置したダイアログ基準としたTransitionアニメーションのサンプル. GitHub Gist: instantly share code, notes, and snippets.

これを実行するとこんな感じになる。

ダイアログの左上を原点に回転とスケール、後フェードをかけている。

コメント

タイトルとURLをコピーしました