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

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

著書紹介

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Akira Hayashi (林 晃)のアバター Akira Hayashi (林 晃) Representative(代表), Software Engineer(ソフトウェアエンジニア)

アールケー開発代表。Appleプラットフォーム向けの開発を専門としているソフトウェアエンジニア。ソフトウェアの受託開発、技術書執筆、技術指導・セミナー講師。note, Medium, LinkedIn
-
Representative of RK Kaihatsu. Software Engineer Specializing in Development for the Apple Platform. Specializing in contract software development, technical writing, and serving as a tech workshop lecturer. note, Medium, LinkedIn

目次