Flutterバージョン3.0から、Macのデスクトップアプリケーションが正式にサポートされるようになりました。
私自身、Macアプリケーションの開発者として、Mac特有の機能が実装できるかに興味を持ちました。Macらしい機能の最たるものの1つがメニューバーだと思います。
そこでGitHubのFlutterのイシューやリリースノートを確認すると、以下のプルリクが見つかりました。
Flutter 3.0のリリースノートにも以下の記載があります。
Implements a PlatformMenuBar widget and associated data structures by @gspencergoog in https://github.com/flutter/flutter/pull/100274
Flutter 3.0 release notes より引用
Macのメニューバーが作れる様になったことが分かったので、詳細を確認するべく、サンプルコードを作りました。サンプルコードはGitHubに置いています。
メニューバーを構成するクラス
メニューバーを構成するクラスは以下のクラスです。
PlatformMenuBarPlatformMenuPlatformMenuItemGroupPlatformMenuItem
このようなMacアプリらしいメニューバーを作れます。

メニューバーを作る
PlatformMenuBarクラスを使います。PlatformMenuBarクラスはStatefulWidgetクラスを継承するウィジェットです。PlatformMenuBarクラスを使ってメニューバーを作成するために以下のようなコードを記述しました。ただし、この段階ではまだメニューバーは空の状態です。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return PlatformMenuBar(
menus: <MenuItem> [
],
body: Center(
// 省略
),
);
}
}
メニューを作る
FileメニューやEditメニューなど、メニューごとにPlatformMenuのインスタンスを作成し、これらを配列に格納してPlatformMenuBarのコンストラクターのmenus引数に渡します。
return PlatformMenuBar(
menus: <MenuItem>[
PlatformMenu(/*省略*/), // Application Menu
PlatformMenu(/*省略*/), // File Menu
PlatformMenu(/*省略*/), // Edit Menu
PlatformMenu(/*省略*/), // View Menu
PlatformMenu(/*省略*/), // Window Menu
],
body: Center(
),
);
アプリケーションメニュー
Macのメニューバーにあるメニューの中で、どのアプリにも共通して存在するメニューがあります。次の2つです。
- アップルメニュー
- アプリケーションメニュー
アップルメニューはOSが制御しているため、アプリケーションから変更することはできません。
アプリケーションメニューはアップルメニューの隣にある、アプリ名がタイトルのメニューです。
menusにメニューの配列を指定します。指定する配列に入っている順番で、メニューバーにメニューが作成されます。配列の先頭がアプリケーションメニューとなります。
メニューのタイトルとアイテムの指定
PlatformMenuのコンストラクターのlabel引数にメニュータイトルを指定します。menus引数にはメニューに入れるメニューアイテムを指定します。
たとえば、Newというメニューアイテムを一つだけ持った、Fileメニューは次のようになります。
PlatformMenu(
label: 'File',
menus: <MenuItem>[
PlatformMenuItemGroup(
members: <MenuItem>[
PlatformMenuItem(
label: 'New',
),
],
),
],
),
Macアプリの一般的なメニュー
一般的なMacアプリのメニュー構成は次のようになります。
- アップルメニュー
- アプリケーションメニュー
- Fileメニュー
- Editメニュー
- Viewメニュー
- この辺にアプリ独自のメニュー
- Windowメニュー
- Helpメニュー
しかしながら、必ずしもこの通りのメニュー構成になっていなくとも良いので、Flutterでも作らなかったメニューが自動的には作られません。
一つもメニューを作らなくても、作られてしまうメニューは、アップルメニューとアプリケーションメニューだけです。アプリケーションメニューも中身は勝手には作られません。
このような一般的なメニュー構成についての詳細は、Human Interface Guidelinesにて解説されています。
Human Interface Guidelines, Menu Bar Menus
セパレーターを作る
AppKitでは、メニューアイテムとセパレーターはどちらもNSMenuItemクラスのインスタンスです。つまり、セパレーターも一つのメニュー項目であるという考え方になっています。
Flutterでは異なります。セパレーターはメニュー内のアイテムをグループに分割するものである、という考え方になっています。たとえば、次のようなセパレーターを2つ持ったメニューは、3つのグループに分割されていると考えます。
- Item 1
- Item 2
- セパレーター
- Item 3
- Item 4
- セパレーター
- Item 5
グループを作るにはPlatformMenuItemGroupクラスを使用します。上の例をコードにすると次のような感じです。
PlatformMenu(
label: 'MyMenu',
menus: <MenuItem>[
PlatformMenuItemGroup(
members: <MenuItem>[
PlatformMenuItem(/*省略*/), // Item 1
PlatformMenuItem(/*省略*/), // Item 2
],
),
PlatformMenuItemGroup(
members: <MenuItem>[
PlatformMenuItem(/*省略*/), // Item 3
PlatformMenuItem(/*省略*/), // Item 4
],
),
PlatformMenuItemGroup(
members: <MenuItem>[
PlatformMenuItem(/*省略*/), // Item 5
],
),
],
),
グループとグループの区切りとして、セパレーターが挿入されます。
メニューアイテムを作る
メニューアイテムを作るには PlatformMenuItem クラスを使用します。
PlatformMenuItem(
label: 'Item Title',
),
メニューアイテムのタイトルを引数labelに指定します。
メニューが選択されたときの処理の実装
メニューが選択されたときの処理は、引数onSelectedで指定します。
PlatformMenuItem(
label: 'Increment',
onSelected: () {
// メニューが選択されたときの処理
_incrementCounter();
},
),
このコード例では、Incrementメニューアイテムが選択されると、_incrementCounter()メソッドが実行されます。
ショートカットキーを割り当てる
ショートカットキーを割り当てるには、引数shortcutにショートカットを指定します。ショートカットは、SingleActivatorクラスを使います。
PlatformMenuItem(
label: 'Increment',
shortcut: const SingleActivator(LogicalKeyboardKey.keyI, meta: true),
onSelected: () {
_incrementCounter();
},
),
このコード例では、Commandキーを押しながらIキーでメニューアイテムが選択されます。
モディファイアキーと一緒に押すキーは、LogicalKeyboardKeyクラスで指定します。モディファイアキーは、SingleActivatorコンストラクターの引数で指定します。省略しているものも含めて、次のような引数があります。
controlコントロールキーshiftシフトキーaltオプションキー (Altキー)metaコマンドキー
プラットフォーム特有のメニューアイテム
アプリケーションメニューの「サービス」や「すべてを表示」などはアプリ側では実装できません。これらのプラットフォーム特有の機能に対応したメニューアイテムを作るには、PlatformProvidedMenuItemクラスを使用します。
たとえば、「サービス」メニューアイテムは次のようになります。
PlatformMenuItemGroup(
members: <MenuItem>[
if (PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.servicesSubmenu))
const PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.servicesSubmenu),
],
),
hasMenu()メソッドは引数に指定したメニューアイテムが、実行中のデバイスに対応しているかを返します。
PlatformProvidedMenuItemコンストラクターの引数typeに作成するメニューアイテムを指定します。この記事執筆時点で、macOS特有のメニューアイテムは次の通りです。
about「○○について」というアプリメニューの先頭にあるメニューアイテムquitアプリ終了servicesSubmenuアプリメニューのサービスhideアプリを隠すhideOtherApplications他のアプリを隠すshowAllApplications全てのアプリを表示startSpeaking読み上げ開始stopSpeaking読み上げ停止toggleFullScreenフルスクリーン表示minimizeWindowウインドウをしまうzoomWindowウインドウをズームarrangeWindowsInFrontアプリのウインドウを全て手前に移動
Flutter 3.0時点では、macOS特有のメニューアイテムしか定義されていません。
サンプルアプリ
GitHubにサンプルアプリを置きました。実際のコードについては、サンプルアプリ参照してください。











