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,有需要的可以下载下来查看学习。

posted @ 2022-03-31 09:26  菠萝橙子丶  阅读(1253)  评论(0编辑  收藏  举报