ちょっと前までならFlutterでポップアップメニューと言えばPopupMenuButtonだったが、現在ではMenuAnchorもしくはMenuBarが推奨されている。
PopupMenuButtonのAPI中にも「Updating to MenuAnchor」とそれらしきことが書かれているし。
基本的な使い方については、API説明書に書かれているので、そこに書かれていないことと、2025/01/4現在不具合的なことについて記述する。
配置可能なウィジェット
MenuAnchorで配置可能なウィジェットはList<Widget>とList<PopupMenuEntry>だったPopupMenuButtonとは異なり、一応幅広いウィジェットが設定可能になっている。
しかしPopupMenuEntryには派生として用途別にPopupMenuItem PopupMenuDivider CheckedPopupMenuItemと3つのウィジェットが用意されていたが、MenuAnchorには専用としてはMenuItemButtonとSubMenuButtonしかなく、区切り線やチェックボックス(セレクター)はどうやればいいのかという問題が出てくる。
区切り線とチェックボックス
設定可能なのがList<Widget>になるので、区切り線やチェックボックスは、そのウィジェットをそのまま配置すれば問題ない。
区切り線はDividerウィジェットをMenuItemButtonの間に挟むという方法。
上記のような区切り線が表示される。
チェックボックスはちょっと面倒。
MenuItemButton(
onPressed: () {
setState(() {
_toggle = !_toggle;
});
},
leadingIcon: Icon(_toggle ? Icons.check : null),
child: const Text('Check Box1'),
),
上記のような実装になる。
つまり、チェックのON/OFFに関してはleadingIcon側に表示し、タップでON/OFFを変える感じ。
leadingIconにCheckboxを配置するという方法もとれるけど、その場合MaterialDesing3のMenuの仕様とはちょっと離れたデザインになる。
こちらが Checkboxにした場合の画像。
そしてMaterialDesing3の仕様の方。
Column/Rowを使った複合的なウィジェット構成
ここに、Column/Rowを使った複合的に配置したドロップダウンメニューを表示したいんだけど、同実装したらいいかという質問がありその回答があるのだが、この回答のコードについてはそのままでは使えない。
具体的にはRowの中にMenuItemButtonをそのまま配置すると以下のような例外が発生する。
The following assertion was thrown during performLayout():
RenderFlex children have non-zero flex but incoming width constraints are unbounded.
When a row is in a parent that does not provide a finite width constraint, for example if it is in a horizontal scrollable, it will try to shrink-wrap its children along the horizontal axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining space in the horizontal direction.
これに関しては、例外で出るエラーメッセージの通りの対応をすれば解決はする。
RowにはmainAxisSize: MainAxisSize.minを設定し、MenuItemButtonを含む子供に対してFlexibleでラッピングする。
Row(
mainAxisSize: MainAxisSize.min, // 追加
children: [
const Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 10),
child: Icon(
Icons.person,
size: 30,
color: Colors.white,
),
),
const SizedBox(
width: 20,
),
Flexible( // 追加
child: Column(
children: [
MenuItemButton(
child: const Text("Item 1"),
onPressed: () => {},
),
MenuItemButton(
child: const Text("Item 2"),
onPressed: () => {},
),
],
),
)
],
)
上記のような形にする。
MenuItemButtonの構成
MenuItemButtonにはleadingIcon, child, trailingIconと3つのウィジェットを配置できる。
それを配置し、それぞれのウィジェットの区切り位置をWidget Inspectorで表示したのがこちら。
leadingIconの左側とtrailingIconの右側のスペースはAndroidでは11.7dpとほぼ仕様通り、Windows版は8dpとVisualDensityの適用が入っている。
leadingIconとtrailingIconがある場合のchildの左右幅は12dp(Windows版は8dp)となっている。
複数行のMenuItemButtonがある場合、leadingIconとchildは左詰めに配置、trailingIconは右詰に配置される。
SubMenuButtonのメニュー展開を示すアイコンはtrailingIconの右側に配置される。
child部分の整列配置
上の「12 Revert 789」と「abcd Setting 3」を見てわかる通り、そのままではchildは整列配置されない。
leadingIconのウィジェットをサイズ指定する必要がある。
TextならTextPainterを使った事前幅計算をするしかないのだけど、テキストスタイルの取得が結構難しいので、必要性を考慮しないといけないかも。
バグらしきもの
2点ほど見つけた。どちらもFlutterのIssueに入っているので、将来的には修正されると思う。
MenuAncharのbuildeの呼び出しタイミング
MenuControllerの状態変化のタイミングでも呼び出されるかなと思ったのだけど、それは無理だった。
例えば、ドロップダウンメニューが表示されたときに、アンカーウィジェットのアイコンを変化させるような動きをさせたかったのだけど、無理だった。
こちらの方は修正済みで、次の版(3.27.1の次かな)にはいってくると思う。
ドロップダウンのアニメーション表示
仕様的にはアニメーションで表示させるみたいなんだけど、実装されていない。
こちらもIssueに入っている。
Pullリクエストが入っているんだけど、まだ取り込まれていないみたい。
必要性がよくわからないもの
childFocusNodeパラメータ
サンプルを見るに、builder内でフォーカスをアンカーウィジェットに設定している。
このような書き方がサンプルのテンプレになっている。
このサンプルのような書き方がなくても動きはする。
Windows版ではアンカー部分を触ると、フォーカス色になるということで動作しているのがわかるのだけど、Android版ではフォーカス色にならない(フォーカスは移っている模様)。
同じような仕組みのMenuBarにはフォーカス操作の機能は持っていない。
初期実装時から存在はしていたのだけど、どういった必要性があり、パラメータとして用意したのかが不明。
コメント