Flutter-PopupMenuButton

PopupMenuButtonでカスケードメニューの作り方。

初めに

一部では、サブメニューとかネストとか書かれているがFlutterのポップアップメニューでカスケードメニューが実現できるかどうか調査していたのだけど、その実現方法が分かった。
実装方法としてはそれほど難しくはなかったのだけど、いくつかだけ注意しなければいけないことがあった。

実装方法

PopupMenuButtonの項目として追加するPopupMenuItemのchildにPopupMenuButtonを設定することで可能。

実装時の注意点は以下の様になる。

  1. PopupMenuItemにvalueに値を設定しない。
    valueに値が設定されない場合、該当箇所のメニューを選択しても、onSelectedが呼び出されず表示されているポップアップメニューもクローズされない。
  2. メニューに表示される文字は、PopupMenuButtonのchildに設定する。

    表示される場所は上のような感じ。
    childの設定がない場合、ポップアップメニュー起動用のデフォルトボタンが表示される。
  3. 2のchildのテキストの大きさに注意する。
    childにTextだけだとカスケードメニューで起動するためのタッチ領域が小さくなる。
    TextをSizedBox等で囲んで少しでも大きくしておいたほうが良い。

    上の図ではSizedBoxの幅を無限にした結果、選択できる領域が横方向に大きくなった。
    縦幅も大きくした方がいいのかもしれなかったのだけど、適切な大きさが分からなかったので、値の設定はしていない。
    ここらあたりもチューニングしてけばいいかもしれない。
    縦幅はkMinInteractiveDimensionという定数を使うのが良いらしいとのこと。
    これにより以下の様にメニュー内の縦方向にも選択領域が広がるようになった。
  4. カスケード側のonSelectedでNavigator.of(context).pop();を実行する。
    ポップアップメニューはonSelectedが呼び出されるとクローズされるのだけど、親側は当然だけど親のonSelectedで処理されていないのでクローズされない。
    そこで親もクローズさせるために、上の呼び出しを入れることになる。

サンプルコード

以下のサンプルコードを実施すると、以下の様なカスケードメニューが実現できる。

import 'package:flutter/material.dart';

class CascadeMenu extends StatelessWidget {
  const CascadeMenu({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Spacer test"),
        actions: [
          PopupMenuButton<String>(
            onSelected: (value) => debugPrint(value),
            itemBuilder: (_) {
              return [
                const PopupMenuItem<String>(
                  value: "data",
                  child: Text("data"),
                ),
                PopupMenuItem<String>(
                  child: PopupMenuButton<String>(
                    child: SizedBox(
                      height: kMinInteractiveDimension,
                      width: double.infinity,
                      child: Container(
                          alignment: Alignment.centerLeft,
                          child: const Text("sub")),
                      ),
                    ),
                    itemBuilder: (_) {
                      return [
                        const PopupMenuItem<String>(
                          value: "subdata1",
                          child: Text("subdata1"),
                        ),
                        const PopupMenuItem<String>(
                          value: "subdata2",
                          child: Text("subdata1"),
                        ),
                      ];
                    },
                    onSelected: (value) {
                      debugPrint(value);
                      Navigator.of(context).pop();
                    },
                  ),
                ),
              ];
            },
          )
        ],
      ),
      body: const Center(child: Text("Hoge")),
    );
  }
}

 

コメント

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