FlutterでMacアプリのメニューバーを作る

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にサンプルアプリを置きました。実際のコードについては、そちらを参照してください。

スポンサーリンク
最新情報をチェックしよう!

Flutterの最新記事8件