?在 Flutter 桌面應用開發中,context_menu
和 contextual_menu
是兩款常用的右鍵菜單插件,各有特色。以下是對它們的對比分析:?
context_menu
-
集成方式:?通過
ContextMenuArea
組件包裹目標組件,定義菜單項。?掘金 -
菜單定義:?使用
builder
返回一個List<Widget>
,通常為ListTile
,支持圖標、文字和點擊事件。?掘金 -
適用場景:?適合需要快速實現簡單右鍵菜單的場景,集成方便,適用于大多數桌面應用。?掘金
contextual_menu
-
集成方式:?需要手動監聽鼠標右鍵事件,并調用
popUpContextualMenu()
方法顯示菜單。?掘金 -
菜單定義:?使用
Menu
和MenuItem
結構,支持普通項、復選框、分隔符和子菜單等多種類型。?掘金 -
適用場景:?適合需要復雜菜單結構(如多級菜單、復選項)的應用,提供更高的自定義能力。?
總結對比
特性 | context_menu | contextual_menu |
---|---|---|
集成方式 | 使用組件包裹目標組件,集成簡單 | 手動監聽事件,調用方法顯示菜單,集成復雜 |
菜單結構 | 簡單,適合基本菜單 | 復雜,支持多級菜單、復選項等 |
自定義能力 | 限制較多,主要通過 ListTile 實現 | 高度自定義,支持多種菜單項類型 |
適用場景 | 快速實現基本右鍵菜單 | 實現復雜、結構化的右鍵菜單 |
建議選擇
-
選擇
context_menu
:?如果你需要快速集成一個簡單的右鍵菜單,且菜單項較為基礎,context_menu
是一個不錯的選擇。? -
選擇
contextual_menu
:?如果你的應用需要復雜的菜單結構,如多級菜單、復選項等,contextual_menu
提供了更強大的功能和靈活性。
contextmenu
hello word
引入依賴
contextmenu: ^3.0.0
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),),home: const MyHomePage(title: 'Flutter Demo Home Page'),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary,title: Text(widget.title),),body: ContextMenuArea(child: Container(color: Colors.grey,padding: EdgeInsets.all(20),child: Text("在這里右鍵"),),builder: (BuildContext context) {return [Container(padding: EdgeInsets.all(10), child: Text('自定義菜單')),ListTile(title: Text("點擊"),onTap: () {ScaffoldMessenger.of(context,).showSnackBar(SnackBar(content: Text("點擊了")));},),];},), // This trailing comma makes auto-formatting nicer for build methods.);}
}
自定義彈出位置

///完全自定義位置GestureDetector(onSecondaryTapDown:(details) => showContextMenu(details.globalPosition,context,(BuildContext context) {return [Container(padding: EdgeInsets.all(10),child: Text('自定義菜單'),),ListTile(title: Text("點擊"),onTap: () {ScaffoldMessenger.of(context,).showSnackBar(SnackBar(content: Text("點擊了")));},),];},0.0,200.0,),child: Container(padding: EdgeInsets.all(15),color: Colors.green,child: Text('Tap!'),),),
contextual_menu
hello word
https://pub.dev/packages/contextual_menu
contextual_menu: ^0.1.2
GestureDetector(onSecondaryTapDown: (details) {Menu menu = Menu(items: [MenuItem(label: 'Copy',onClick: (_) {print('Clicked Copy');},),MenuItem(label: 'Disabled item', disabled: true),MenuItem.checkbox(key: 'checkbox1',label: 'Checkbox1',checked: true,onClick: (menuItem) {print('Clicked Checkbox1');menuItem.checked = !(menuItem.checked == true);},),MenuItem.separator(),],);popUpContextualMenu(menu, placement: Placement.bottomLeft);},child: Container(padding: EdgeInsets.all(15),color: Colors.green,child: Text('Tap!'),),),
popUpContextualMenu
可以看到我們本身沒有傳遞position參數,那么他是怎么感知我鼠標點擊的位置呢
他是通過window記錄的鼠標點擊位置來展示的
NSWindow.mouseLocationOutsideOfEventStream
是 macOS 平臺(AppKit 框架)中的一個屬性,用于獲取當前鼠標在指定窗口中的坐標位置,而不是通過事件觸發獲取的。這個方法非常實用,尤其是在沒有發生鼠標事件時,仍然需要獲取當前鼠標位置的場景下。
var mouseLocationOutsideOfEventStream: NSPoint { get }
-
返回值:一個
NSPoint
,表示鼠標相對于窗口坐標系的位置。 -
類型:
NSWindow
的實例屬性。
自定義ContextMenuArea
在contextmenu中我們看到ContextMenuArea包裝使用非常簡單,我們這里用contextual_menu也包裝一個
import 'dart:ui';import 'package:contextual_menu/contextual_menu.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';typedef ContextMenuBuilder = List<MenuItem> Function(BuildContext context);class ContextualMenuArea extends StatefulWidget {final Widget child;final ContextMenuBuilder builder;const ContextualMenuArea({super.key,required this.child,required this.builder,});@overrideState<StatefulWidget> createState() {return ContextualMenuAreaState();}
}class ContextualMenuAreaState extends State<ContextualMenuArea> {bool _shouldReact = false;Offset? _position;@overrideWidget build(BuildContext context) {return Listener(child: widget.child,onPointerDown: (details) {///kSecondaryMouseButton 0x02 次鍵(一般是右鍵)///PointerDeviceKind.mouse 輸入來源是鼠標_shouldReact =details.kind == PointerDeviceKind.mouse &&details.buttons == kSecondaryMouseButton;},onPointerUp: (details) {if (!_shouldReact) return;_position = details.position;_handleClickPopUp();},);}void _handleClickPopUp() {popUpContextualMenu(Menu(items: widget.builder(context)),position: _position,placement: Placement.bottomRight,);}
}
使用方式
ContextualMenuArea(child: Container(padding: EdgeInsets.all(20),color: Colors.grey,child: Text("ContextualMenuArea"),),builder: (context) {return [MenuItem.submenu(label: "復制",submenu: Menu(items: [MenuItem.checkbox(label: "復制全部", checked: false),MenuItem.checkbox(label: "復制當前", checked: true),],),),MenuItem.separator(),MenuItem(label: "粘貼"),];},)
運行效果