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),
),
),
);
这个时候我们只能看到我们设置的图片,点击鼠标右键是没反应的
接下来我们来为图片添加右键点击弹出上下文菜单选项,我们需要用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,这个主要针对链接,里面已经设置了菜单项和方法:
第一个选项在浏览器打开可以自己试一下。
我们再来定义一个文本内容:
ContextMenuRegion(
contextMenu: TextContextMenu(data: _title),
child: Text(_title, style: const TextStyle(fontSize: 24)),
),
右键菜单都是英语,我想要换成中文怎么办?那我们就自定义属于我们自己的菜单选项就行。
自定义菜单需要用到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('歌曲列表'),
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),
),
目前我们只使用了ContextMenuOverlay的child属性,我们接下来啦看看其他的:
ContextMenuOverlay(
cardBuilder: (_, children) => Container(
color: Colors.lightBlueAccent,
child: Column(children: children),
),
child:...
),
buttonBuilder: (_, config, [__]) => TextButton(
onPressed: config.onPressed,
child: Text(
'${config.label} - 银临',
style: const TextStyle(color: Colors.white),
),
),
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'),
),
该插件还有一个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)),
),
为选项添加事件:
child: ContextMenuRegion(
onItemSelected: (item) => BotToast.showText(text: '你选中了$item'),
menuItems: ...,
child: ...,
),
我们可以发现,最终点击获得的是一个MenuItem对象,它有以下几个属性:
required String title
:选项的标签void Function()? onSelected
:被选中的事件Object? action
:不知道传入有啥用😪List items = const []
:子选项
所以我们可以通过item.title
来获取具体选中的对象,也可以直接把方法直接写在MenuItem上:
child: ContextMenuRegion(
...
menuItems: [
...
MenuItem(title: '复制', onSelected: () => BotToast.showText(text: '复制成功')),
...
],
...
),
呃...😥什么都没发生,以后还是使用ContextMenuRegion的onItemSelected方法吧。
最后添加一个没有选中选项情况下的事件:
child: ContextMenuRegion(
onDismissed: () => BotToast.showText(text: '你没有选择任何选项'),
onItemSelected: (item) => BotToast.showText(text: '你选中了${item.title}'),
menuItems: ...,
child: ...,
),
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,
),
),
],
);
}
点击右键是可以显示菜单了,但是点击事件好像并没有效果。要想调用此方法需要进行以下操作
_showContext() async {
final _menu = await showContextMenu(
menuItems: [...]
);
}
然后使用以下方法
_menu.onTap?.call();
或者以下方法
if (_menu == null) return;
BotToast.showText(text: _menu!.title ?? '');
注意:该插件中的快捷键只能用来显示,并不能调用方法。
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
:子菜单项
MenuItem
有以下多个属性:
String? key
:组件唯一标识String type
:菜单项的类型,主要有normal、checkbox、separator、submenu几个值。默认为normalString? label
:菜单项显示的文本String? sublabel
:菜单项显示的二级文本String? toolTip
:菜单项的提示String? icon
:菜单项的图标(猜测,目前该插件版本还有待更新,文档并没有对该值进行描述)bool? checked
:是否被选择。仅在type为checkbox时生效bool disabled = false
:是否禁用选项。默认为falseMenu? 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);
}
popUpContextualMenu有一个设置偏移量的属性,我们看看它是以哪里为标准的
popUpContextualMenu(
_menu,
position: Offset.zero,
);
popUpContextualMenu还可以通过placement属性来设置菜单的显示位置,如果有设置position,则该属性以position所在位置为标准
popUpContextualMenu(
_menu,
position: Offset.zero,
placement: Placement.topLeft,
);
虽然MenuItem还有其他属性,但是设置了并没有效果,也许还没完成。
🛫OK,以上就是这篇文章的全部内容,仅针对插件的当前版本,并不能保证适用于以后插件用法的更新迭代。
最后,感谢TheOneWithTheBraid 、gskinnerTeam、lesnitsky、lijy91、nfsxreloader对以上插件的开发和维护😁。本应用代码已上传至 github 和 gitee,有需要的可以下载下来查看学习。