Flutter桌面端開發——上下文菜單


注意:查看本文章前請先查看更新日志,以至於該文章適合插件的最新版本

更新日志
詳情 日期
添加了contextual_menu插件的用法 2022-05-09
添加了desktop_context_menu插件的用法 2022-05-07

關於上下文菜單的插件還有一個 native_context_menu_ng ,支持所有電腦端的系統。對於該插件的用法暫時沒有時間學習和編寫,有需要的自己去插件主頁查看,有中文文檔。

context_menus

安裝🛠

點擊context_menus獲取最新版本。以下是在編寫本文章時的最新版本:

context_menus: ^1.0.1

使用🥣

要想在全局使用,就得包裹住MaterialApp:

return ContextMenuOverlay(
  child: MaterialApp(...)
);

局部使用包裹住相應的組件即可。

ContextMenuOverlay

  • Key? key:傳入一個唯一的鍵
  • required Widget child:子組件
  • Widget Function(BuildContext, List )? cardBuilder:自定義上下文菜單樣式
  • Widget Function(BuildContext, ContextMenuButtonConfig, [ContextMenuButtonStyle?])? buttonBuilder:自定義上下文菜單按鈕
  • Widget Function(BuildContext)? dividerBuilder:上下文菜單間的間隔
  • ContextMenuButtonStyle? buttonStyle:設置上下文菜單按鈕的樣式
ContextMenuOverlay(
  child: Center(
    child: ConstrainedBox(
      constraints: const BoxConstraints(
        maxWidth: 800,
        maxHeight: 450,
      ),
      child: Image.network(_image),
    ),
  ),
);

這個時候我們只能看到我們設置的圖片,點擊鼠標右鍵是沒反應的

image

接下來我們來為圖片添加右鍵點擊彈出上下文菜單選項,我們需要用ContextMenuRegion包裹住我們需要彈出右鍵菜單的對象:

ContextMenuOverlay(
  child: ContextMenuRegion(
    contextMenu: LinkContextMenu(url: src),
    child: Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          maxWidth: 800,
          maxHeight: 450,
        ),
        child: Image.network(src),
      ),
    ),
  ),

我們來看一下ContextMenuRegion的屬性:

  • Key? key:🤫
  • required Widget child:傳遞一個子組件
  • required Widget contextMenu:設置右鍵菜單
  • bool isEnabled = true:是否啟用彈出菜單
  • bool enableLongPress = true:是否啟動觸屏設備長按彈出菜單

在這里我使用了一個預設的LinkContextMenu,這個主要針對鏈接,里面已經設置了菜單項和方法:

image

第一個選項在瀏覽器打開可以自己試一下。

我們再來定義一個文本內容:

ContextMenuRegion(
  contextMenu: TextContextMenu(data: _title),
  child: Text(_title, style: const TextStyle(fontSize: 24)),
),

image

右鍵菜單都是英語,我想要換成中文怎么辦?那我們就自定義屬於我們自己的菜單選項就行。

自定義菜單需要用到GenericContextMenu對象,它有以下幾個屬性:

  • Key? key:🤫
  • required List buttonConfigs:配置菜單選項
  • bool injectDividers = false:將分隔線交錯到菜單中,使用 null 作為標記來指示某個位置的分隔線
  • bool autoClose = true:點擊選項后是否自動關閉
ContextMenuRegion(
  contextMenu: GenericContextMenu(
    buttonConfigs: List.generate(
      _songs.length,
      (index) => ContextMenuButtonConfig(
        _songs[index],
        onPressed: () =>
            BotToast.showText(text: '播放${_songs[index]}'),
      ),
    ).toList(),
  ),
  child: const Text('歌曲列表'),

image

ContextMenuButtonConfig對象有5個屬性:

  • String label:設置顯示的標簽
  • required void Function()? onPressed:設置點擊后調用的方法
  • String? shortcutLabe:快捷鍵標簽
  • Widget? icon:圖標
  • Widget? iconHover:鼠標放上標簽后的圖標
ContextMenuButtonConfig(
  _songs[index],
  onPressed: () =>
      BotToast.showText(text: '播放${_songs[index]}'),
  shortcutLabel: 'ctrl+F${index + 1}',
  icon: const Icon(Icons.album, size: 18),  // 設置成18圖標就會居中和文字對齊
  iconHover: const Icon(Icons.play_circle_fill_rounded, size: 18),
),

image

目前我們只使用了ContextMenuOverlaychild屬性,我們接下來啦看看其他的:

ContextMenuOverlay(
  cardBuilder: (_, children) => Container(
    color: Colors.lightBlueAccent,
    child: Column(children: children),
  ),
  child:...
),

image

buttonBuilder: (_, config, [__]) => TextButton(
  onPressed: config.onPressed,
  child: Text(
    '${config.label} - 銀臨',
    style: const TextStyle(color: Colors.white),
  ),
),

image

dividerBuilder: (context) => Container(height: 2, width: double.maxFinite, color: Colors.blue)

設置了毫無反應...😑

contextmenu

安裝🛠

點擊contextmenu獲取最新版本。以下是在編寫本文章時的最新版本:

contextmenu: ^3.0.0

使用🥣

將需要使用上下文菜單的組件用ContextMenuArea包裹起來,該組件有5個屬性:

  • Key? key:🤫
  • required Widget child:需要上下文菜單的子元素
  • required List Function(BuildContext) builder:上下文菜單選項
  • double verticalPadding = 8:上下文菜單的垂直內邊距
  • double width = 320:上下文菜單的寬
child: ContextMenuArea(
  builder: (context) => List.generate(
    _friends.length,
    (index) => ListTile(
      title: Text('給${_friends[index]}打電話'),
      onTap: () => BotToast.showText(text: '正在聯系${_friends[index]}'),
    ),
  ).toList(),
  child: Image.asset('assets/images/pdx.jpg'),
),

image

該插件還有一個showContextMenu方法,可以用來設置在其他點擊事件里面。

native_context_menu

該插件在pub.dev中標記為[UNIDENTIFIED],暫不建議使用

安裝🛠

點擊native_context_menu獲取最新版本。以下是在編寫本文章時的最新版本:

native_context_menu: ^0.2.1+4

使用🥣

需要上下文菜單的組件必須使用ContextMenuRegion包裹起來,該組件有以下幾個屬性:

  • Key? key:🤫
  • required Widget child:需要上下文菜單的子元素
  • required List menuItems:上下文菜單選項
  • void Function(MenuItem)? onItemSelected:選中菜單的事件
  • void Function()? onDismissed:打開上下文菜單沒有選擇時觸發
  • Offset menuOffset = Offset.zero:菜單的位置
child: ContextMenuRegion(
  menuItems: [
    MenuItem(title: '全選'),
    MenuItem(title: '復制'),
    MenuItem(title: '剪切'),
    MenuItem(title: '粘貼'),
    MenuItem(
      title: '舉報',
      items: [
        MenuItem(title: '發郵箱'),
        MenuItem(title: '打電話'),
      ],
    ),
  ],
  child: Text(_text, style: const TextStyle(height: 1.5)),
),

image

為選項添加事件:

child: ContextMenuRegion(
  onItemSelected: (item) => BotToast.showText(text: '你選中了$item'),
  menuItems: ...,
  child: ...,
),

image

我們可以發現,最終點擊獲得的是一個MenuItem對象,它有以下幾個屬性:

  • required String title:選項的標簽
  • void Function()? onSelected:被選中的事件
  • Object? action:不知道傳入有啥用😪
  • List items = const []:子選項

所以我們可以通過item.title來獲取具體選中的對象,也可以直接把方法直接寫在MenuItem上:

child: ContextMenuRegion(
  ...
  menuItems: [
    ...
    MenuItem(title: '復制', onSelected: () => BotToast.showText(text: '復制成功')),
    ...
  ],
  ...
),

image

呃...😥什么都沒發生,以后還是使用ContextMenuRegiononItemSelected方法吧。

最后添加一個沒有選中選項情況下的事件:

child: ContextMenuRegion(
  onDismissed: () => BotToast.showText(text: '你沒有選擇任何選項'),
  onItemSelected: (item) => BotToast.showText(text: '你選中了${item.title}'),
  menuItems: ...,
  child: ...,
),

image

desktop_context_menu

安裝🛠

點擊desktop_context_menu獲取最新版本。以下是在編寫本文章時的最新版本:

desktop_context_menu: ^0.1.1

使用🥣

這個插件並沒有一個組件用來包裹需要調用右鍵菜單的內容,所以需要我們自己監聽鼠標右鍵事件。

我們需要判斷用戶按下的是鼠標右鍵

bool _openContext = false;
Listener(
	onPointerDown: (e) {
    _openContext = e.kind == PointerDeviceKind.mouse &&
        e.buttons == kSecondaryMouseButton;
    setState(() {});
  },
  onPointerUp: (e) {
    if (_openContext) {
      _showContext();
      _openContext = false;
    }
  },
),

顯示右鍵菜單的內容就是_showContext方法,我們必須調用該插件的showContextMenu方法

_showContext() async {
  await showContextMenu(
    menuItems: []
  );
}

該方法里需要傳入一個數組用來顯示上下文菜單中的內容。分別可以傳入ContextMenuItem和ContextMenuSeparator。

ContextMenuSeparator就是一條菜單的分割線,這里不多贅述。我們來看看ContextMenuItem。

ContextMenuItem有3個參數:

  • String? title:顯示的標題
  • void Function()? onTap:點擊事件
  • SingleActivator? shortcut:快捷鍵
_showContext() async {
  await showContextMenu(
    menuItems: [
      ContextMenuItem(
        title: '新建',
        onTap: () {},
        shortcut: SingleActivator(
          LogicalKeyboardKey.keyN,
          meta: Platform.isMacOS,
          control: Platform.isWindows,
        ),
      ),
      const ContextMenuSeparator(),
      ContextMenuItem(
        title: '剪切',
        onTap: () {
          BotToast.showText(text: '你按了剪切');
        },
        shortcut: SingleActivator(
          LogicalKeyboardKey.keyV,
          meta: Platform.isMacOS,
          control: Platform.isWindows,
        ),
      ),
      ContextMenuItem(
        title: '復制',
        onTap: () {
          BotToast.showText(text: '你按了復制');
        },
        shortcut: SingleActivator(
          LogicalKeyboardKey.keyC,
          meta: Platform.isMacOS,
          control: Platform.isWindows,
        ),
      ),
      ContextMenuItem(
        title: '粘貼',
        onTap: () {
          BotToast.showText(text: '你按了粘貼');
        },
        shortcut: SingleActivator(
          LogicalKeyboardKey.keyV,
          meta: Platform.isMacOS,
          control: Platform.isWindows,
        ),
      ),
    ],
  );
}

image

點擊右鍵是可以顯示菜單了,但是點擊事件好像並沒有效果。要想調用此方法需要進行以下操作

_showContext() async {
  final _menu = await showContextMenu(
    menuItems: [...]
  );
}

然后使用以下方法

_menu.onTap?.call();

image

或者以下方法

if (_menu == null) return;
BotToast.showText(text: _menu!.title ?? '');

image

注意:該插件中的快捷鍵只能用來顯示,並不能調用方法。

contextual_menu

安裝🛠

點擊contextual_menu獲取最新版本。以下是在編寫本文章時的最新版本:

contextual_menu: ^0.1.4

使用🥣

該插件和desktop_context_menu一樣,需要自己監聽右鍵事件。這里就不多贅述。

在程序中調出上下文菜單需要使用popUpContextualMenu方法,該方法可以傳遞3個參數

  • Menu menu:要顯示的上下文菜單對象
  • Offset? position:上下文菜單對象顯示的偏移量
  • Placement placement:上下文菜單顯示的位置。默認值為Placement.bottomRight

Menu對象需要傳入一個MenuItem數組,MenuItem有以下幾種樣式:

  • MenuItem:普通的菜單項
  • MenuItem.checkbox:復選框菜單項
  • MenuItem.separator:分隔符菜單項
  • MenuItem.submenu:子菜單項

有以下多個屬性:

  • String? key:組件唯一標識
  • String type:菜單項的類型,主要有normal、checkbox、separator、submenu幾個值。默認為normal
  • String? label:菜單項顯示的文本
  • String? sublabel:菜單項顯示的二級文本
  • String? toolTip:菜單項的提示
  • String? icon:菜單項的圖標(猜測,目前該插件版本還有待更新,文檔並沒有對該值進行描述)
  • bool? checked:是否被選擇。僅在type為checkbox時生效
  • bool disabled = false:是否禁用選項。默認為false
  • Menu? submenu:子菜單項
  • void Function(MenuItem)? onClick:菜單項被點擊后的方法
_showContext() {
  Menu _menu = Menu(
    items: [
      MenuItem(label: '風起隴西'),
      MenuItem.separator(),
      MenuItem.submenu(
        label: '9號秘事 第七季',
        sublabel: '第七季',
        submenu: Menu(
          items: [
            MenuItem.checkbox(
              label: '9號秘事 第一季',
              checked: false,
              onClick: (menuItem) {
                BotToast.showText(text: '即將播放《9號秘事 第一季》');
              },
            ),
            MenuItem.checkbox(label: '9號秘事 第二季', checked: false),
            MenuItem.checkbox(label: '9號秘事 第三季', checked: false),
            MenuItem.checkbox(label: '9號秘事 第四季', checked: false),
            MenuItem.checkbox(label: '9號秘事 第五季', checked: false),
            MenuItem.checkbox(label: '9號秘事 第六季', checked: false),
            MenuItem.checkbox(label: '9號秘事 第七季', checked: true),
          ],
        ),
      ),
    ],
  );
  popUpContextualMenu(_menu);
}

image

popUpContextualMenu有一個設置偏移量的屬性,我們看看它是以哪里為標准的

popUpContextualMenu(
  _menu,
  position: Offset.zero,
);

image

popUpContextualMenu還可以通過placement屬性來設置菜單的顯示位置,如果有設置position,則該屬性以position所在位置為標准

popUpContextualMenu(
  _menu,
  position: Offset.zero,
  placement: Placement.topLeft,
);

image

雖然MenuItem還有其他屬性,但是設置了並沒有效果,也許還沒完成。

🛫OK,以上就是這篇文章的全部內容,僅針對插件的當前版本,並不能保證適用於以后插件用法的更新迭代。

最后,感謝TheOneWithTheBraidgskinnerTeamlesnitskylijy91nfsxreloader對以上插件的開發和維護😁。本應用代碼已上傳至 githubgitee,有需要的可以下載下來查看學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM