Flutter

StackでOverflow.visibleで表示したウィジェットがタップできない問題

2019/12/29

背景

Flutterで下記のようUIを作っていました.
そこでStackウィジェットを利用したことで様々な問題にぶつかりました.

私はOverlayを実装することで解決することができましたので,
その方法について紹介していきたいと思います.

IMB hrrbdp

問題の記載内容や解決方法など, 全てにおいてこの記事の上位互換である素晴らしい記事ですので,
英語が得意な方やこの記事ではわからなかった方はこちらを見てください.
Flutter: Adding Animated Overlays to Your App
※一番下でも紹介しております

問題

今回目指すUIの最初の問題は, FloatingActionButton(真ん中の緑色のボタン)は
固定のサイズでないといけない.
というものです.

BottomNavigationBarはFloatingActionButtonのサイズにより凹んでいます.
この凹ませる処理はFlutterのデフォルトウィジェット側でよしなにやってくれるのですが,
そのためにはFloatingActionButtonを小さくする必要があります.
※heightが大きくなってしまうとUIが壊れてしまいます.

そこで私が考えたのは, Stackウィジェットを利用することです.
Stackウィジェットを利用することで, 複数のWidgetを重ねて表示することができます.

Stackウィジェット内のボタンたちをアニメーション表示してみると...
そうです. 表示されません.

Stackウィジェットは親ウィジェット領域を超えると, 表示されないのがデフォルトです.
そのため, overflowプロパティにOverflow.visibleを指定してあげると表示されます.

しかし, タップしても反応しません.
そうです. Stackウィジェットで親ウィジェット領域外で表示されるウィジェットはタップできません.
これが2つ目の問題です.

調べてみるとFlutterのissueにこんなものが...
Document that widgets in the overflow of stack do not respond to gestures

中を見てみるとわかることは以下の2つ.

  • これは仕様だからリファクタリングしろ
  • Overlayを利用することでうまくいったぞ

実装方法

まずはfloatingActionButtonにSizedBoxで大きさを指定した上,
MenuActionButton(今回私が作ったもの)を子ウィジェットに指定します.

class HomeTemplate extends StatelessWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: SizedBox(
        width: 60,
        height: 60,
        child: MenuActionButton(),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      bottomNavigationBar: RoundedBottomNavigation(),
    );
  }
}

MenuActionButtonの重要な部分を赤枠で囲いました.

1つ目の赤枠はFloatingActionButtonがタップされた際に,
表示フラグを変更しOverlayを挿入します.

2つ目の赤枠はOverlayを作成するコードになります.
FloatingActionButtonをタップした際に表示されるボタン達を作成しています.
offsetを利用することで, 自分が表示させたい位置に表示することができます.
※画像内に収めるためにインデントを変えているため見づらくなっております

Screen Shot 2019-12-29 at 4.12.13 PM2 Screen Shot 2019-12-29 at 4.12.27 PM

皆さんに注意していただきたいのは, 今回の僕のロジックだとOverlayが構築されるタイミングは, FloatingActionButtonをクリックした時のみということです.
ですので, いくら親ウィジェットでsetStateを実行したところで, Overlay側ではリビルドされません.

今回, 僕が一番躓いたところはここになります.

Screen Shot 2019-12-29 at 4.28.18 PM

僕は表示/非表示の際にアニメーションがされれば良かったので, 上記のように
生成されたタイミングでのみのアニメーションが実行されるようにしました.

しかし違ったタイミングでアニメーションしたい場合は, 値が変わるたびにOverlayを再生成と挿入する必要があります.

そこら辺をうまくライブラリ化した方がいらっしゃいましたので, そちらの記事をみることをおすすめします.
※うまく再帰するようになっています Flutter: Adding Animated Overlays to Your App

Twitterフォロー待ってます!