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に置いています。
メニューバーを構成するクラス
メニューバーを構成するクラスは以下のクラスです。
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にサンプルアプリを置きました。実際のコードについては、サンプルアプリ参照してください。