Flutter 3.0からMacのデスクトップアプリも正式にサポートされました。
私はMacアプリ開発者なのでMacらしい機能を作る事ができるかということに興味を持ちました。その最たるものの一つがメニューバーだと思います。
そこで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に置いています。
メニューバーを構成するクラス
メニューバーを構成するクラスは以下のクラスです。
PlatformMenuBar
PlatformMenu
PlatformMenuItemGroup
PlatformMenuItem
こんな感じに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にサンプルアプリを置きました。実際のコードについては、そちらを参照してください。