Flutter原生上下文菜单插件native_context_menu_ng的使用

Flutter原生上下文菜单插件native_context_menu_ng的使用

native_context_menu_ng 是一个为Flutter应用程序提供原生右键菜单的插件,支持三大桌面平台(macOS、Windows和Linux)。

特性

  • 支持子菜单;
  • 支持图标(目前仅支持PNG格式以保持平台一致性);
  • 图标大小无需手动指定,它会根据平台硬件和缩放设置自动调整到最佳尺寸;
  • 支持分隔符;
  • 支持启用/禁用菜单项。

macOS

使用方法

步骤1:构建菜单

首先,我们需要构建一个菜单。这可以通过调用 initMenu() 函数来完成。该函数返回一个 NativeMenu 对象。

Future<NativeMenu> initMenu() async {
  // 如果有子菜单,则此项目无操作。
  NativeMenuItem itemNew = NativeMenuItem.simple(title: "New");
  
  // 从资源文件加载图标
  Uint8List iconText = await VenyoreImageUtil.assetImageToUint8List("assets/images/txt.png");
  Uint8List iconWord = await VenyoreImageUtil.assetImageToUint8List("assets/images/word.png");
  Uint8List iconExcel = await VenyoreImageUtil.assetImageToUint8List("assets/images/excel.png");
  Uint8List iconPdf = await VenyoreImageUtil.assetImageToUint8List("assets/images/pdf.png");

  NativeMenu subMenu = NativeMenu();
  subMenu.addItem(NativeMenuItem.withRawIcon(title: "Text", action: "action_text", rawIcon: iconText));
  subMenu.addItem(NativeMenuItem.withRawIcon(title: "Word", action: "action_word", rawIcon: iconWord));
  subMenu.addItem(NativeMenuItem.withRawIcon(title: "Excel", action: "action_excel", rawIcon: iconExcel));
  subMenu.addItem(NativeMenuItem.withRawIcon(title: "PDF", action: "action_pdf", rawIcon: iconPdf));

  itemNew.subMenu = subMenu;

  // 从本地路径加载图标
  // 注意:本地路径图标在应用沙盒内有限制
  String iconPath;
  if (Platform.isMacOS) {
    iconPath = "/Users/parcool/Downloads/paste.png";
  } else if (Platform.isLinux) {
    iconPath = "/home/parcool/Downloads/paste.png";
  } else if (Platform.isWindows) {
    iconPath = "C:\\Users\\parcool\\Downloads\\paste.png";
  } else {
    throw PlatformException(code: "Unsupported platform!");
  }

  NativeMenu menu = NativeMenu();
  menu.addItem(itemNew);
  menu.addItem(NativeMenuItem.simple(title: "New Folder", action: "action_new_folder"));
  menu.addItem(NativeMenuItem.separator());
  menu.addItem(NativeMenuItem.withIcon(title: "Paste", action: "action_paste", icon: iconPath, isEnable: false));

  return menu;
}

步骤2:添加右键菜单

方法1:使用 NativeContextMenuWidget

将您的小部件作为子部件放入 NativeContextMenuWidget 中。

[@override](/user/override)
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Venyore 插件示例应用'),
      ),
      body: FutureBuilder<NativeMenu>(
        future: initMenu(),
        builder: (BuildContext context, AsyncSnapshot<NativeMenu> snapshot) {
          if (snapshot.hasData) {
            return NativeContextMenuWidget(
              actionCallback: (action) {
                final actionString = action.toString();
                // switch (actionString) {
                //   case "action...":
                //     break;
                // }
              },
              menu: snapshot.requireData,
              otherCallback: (method) {
                if (kDebugMode) {
                  print("$method 被调用了!");
                }
              },
              child: const Text("您的自定义小部件"),
            );
          } else if (snapshot.hasError) {
            return const Text("构建 NativeMenu 时出错。");
          } else {
            return const CircularProgressIndicator();
          }
        },
      ),
    ),
  );
}

方法2:直接使用 NativeContextMenuNg

处理鼠标事件并根据需要弹出菜单。

void showNativeContextMenu() async {
  final nativeContextMenuNgPlugin = NativeContextMenuNg()
    ..setMethodCallHandler((call) async {
    switch (call.method) {
      case "onItemClicked":
        final arg = call.arguments as Map<dynamic, dynamic>;
        final actionString = arg["action"].toString();
        // switch (actionString) {
        //   case "action...":
        //     break;
        // }
        break;
      case "menuDidClose":
        if (kDebugMode) {
          print("menuDidClose 被调用了!");
        }
        break;
      default:
        break;
    }
  });

  await nativeContextMenuNgPlugin.showNativeContextMenu(widget.menu);
}

示例代码

以下是完整的示例代码:

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:native_context_menu_ng/native_context_menu_widget.dart';
import 'package:native_context_menu_ng/native_menu.dart';
import 'package:native_context_menu_ng/venyore_image_util.dart';
import 'package:image/image.dart' as img;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  Future<Uint8List> convertBMPtoUint8List(String assetPath, {int? width, int? height}) async {
    final ByteData byteData = await rootBundle.load(assetPath);
    final Uint8List bmpBytes = byteData.buffer.asUint8List();

    if (width != null && height != null) {
      final img.Image? bmpImage = img.decodeImage(bmpBytes);
      if (bmpImage != null) {
        final img.Image bmpImageResized = img.copyResize(bmpImage, width: width, height: height);

        return Future.value(Uint8List.fromList(img.encodePng(bmpImageResized)));
      }
    }
    return Future.value(bmpBytes);
  }

  String? _clickedAction;

  Future<NativeMenu> initMenu() async {
    // 如果有子菜单,则此项目无操作。
    NativeMenuItem itemNew = NativeMenuItem.simple(title: "New");

    // 从资源文件加载图标
    Uint8List iconText = await VenyoreImageUtil.assetImageToUint8List("assets/images/txt.png");
    Uint8List iconWord = await VenyoreImageUtil.assetImageToUint8List("assets/images/word.png");
    Uint8List iconExcel = await VenyoreImageUtil.assetImageToUint8List("assets/images/excel.png");
    Uint8List iconPdf = await VenyoreImageUtil.assetImageToUint8List("assets/images/pdf.png");

    NativeMenu subMenu = NativeMenu();
    subMenu.addItem(NativeMenuItem.withRawIcon(title: "Text", action: "action_text", rawIcon: iconText));
    subMenu.addItem(NativeMenuItem.withRawIcon(title: "Word", action: "action_word", rawIcon: iconWord));
    subMenu.addItem(NativeMenuItem.withRawIcon(title: "Excel", action: "action_excel", rawIcon: iconExcel));
    subMenu.addItem(NativeMenuItem.withRawIcon(title: "PDF", action: "action_pdf", rawIcon: iconPdf));

    itemNew.subMenu = subMenu;

    // 从本地路径加载图标
    // 注意:本地路径图标在应用沙盒内有限制
    String iconPath;
    if (Platform.isMacOS) {
      iconPath = "/Users/parcool/Downloads/paste.png";
    } else if (Platform.isLinux) {
      iconPath = "/home/parcool/Downloads/paste.png";
    } else if (Platform.isWindows) {
      iconPath = "C:\\Users\\parcool\\Downloads\\paste.png";
    } else {
      throw PlatformException(code: "Unsupported platform!");
    }

    NativeMenu menu = NativeMenu();
    menu.addItem(itemNew);
    menu.addItem(NativeMenuItem.simple(title: "New Folder", action: "action_new_folder"));
    menu.addItem(NativeMenuItem.separator());
    menu.addItem(NativeMenuItem.withIcon(title: "Paste", action: "action_paste", icon: iconPath, isEnable: false));

    return menu;
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Venyore 插件示例应用'),
        ),
        body: FutureBuilder<NativeMenu>(
          future: initMenu(),
          builder: (BuildContext context, AsyncSnapshot<NativeMenu> snapshot) {
            if (snapshot.hasData) {
              return NativeContextMenuWidget(
                actionCallback: (action) {
                  final actionString = action.toString();
                  setState(() {
                    _clickedAction = actionString;
                  });
                  // switch (actionString) {
                  //   case "action1":
                  //     break;
                  //   case "action2":
                  //     break;
                  //   default:
                  //     break;
                  // }
                },
                menu: snapshot.requireData,
                otherCallback: (method) {
                  if (kDebugMode) {
                    print("$method 被调用了!");
                  }
                },
                child: Container(
                  color: Colors.grey[350],
                  child: Column(
                    children: [
                      SizedBox(
                          height: 100,
                          child: Align(
                            alignment: Alignment.bottomCenter,
                            child: Text(
                              "点击的菜单动作: $_clickedAction",
                              style: TextStyle(color: Colors.green[600]),
                            ),
                          )),
                      Expanded(
                        child: Center(
                          child: Text('右键点击此处。', style: TextStyle(color: Colors.grey[500])),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            } else if (snapshot.hasError) {
              return const Text("构建 NativeMenu 时出错。");
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
      ),
    );
  }
}

更多关于Flutter原生上下文菜单插件native_context_menu_ng的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter原生上下文菜单插件native_context_menu_ng的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


native_context_menu_ng 是一个 Flutter 插件,用于在 Flutter 应用中显示原生平台的上下文菜单。它允许开发者在不离开 Flutter 环境的情况下,使用平台原生的上下文菜单功能。这对于需要在特定上下文(例如长按某些元素)中显示菜单的场景非常有用。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 native_context_menu_ng 依赖:

dependencies:
  flutter:
    sdk: flutter
  native_context_menu_ng: ^1.0.0  # 请使用最新版本

然后,运行 flutter pub get 来安装插件。

使用插件

  1. 导入插件

    在你的 Dart 文件中导入 native_context_menu_ng 插件:

    import 'package:native_context_menu_ng/native_context_menu_ng.dart';
    
  2. 创建上下文菜单

    你可以使用 NativeContextMenu 类来创建上下文菜单。以下是一个简单的示例,展示如何在长按某个部件时显示上下文菜单:

    import 'package:flutter/material.dart';
    import 'package:native_context_menu_ng/native_context_menu_ng.dart';
    
    class ContextMenuExample extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Native Context Menu Example'),
          ),
          body: Center(
            child: GestureDetector(
              onLongPress: () async {
                final menu = NativeContextMenu(
                  items: [
                    NativeContextMenuItem(
                      title: 'Copy',
                      onSelected: () {
                        print('Copy selected');
                      },
                    ),
                    NativeContextMenuItem(
                      title: 'Paste',
                      onSelected: () {
                        print('Paste selected');
                      },
                    ),
                    NativeContextMenuItem(
                      title: 'Delete',
                      onSelected: () {
                        print('Delete selected');
                      },
                    ),
                  ],
                );
                await menu.show(context);
              },
              child: Container(
                padding: EdgeInsets.all(20),
                color: Colors.blue,
                child: Text(
                  'Long press here',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    void main() {
      runApp(MaterialApp(
        home: ContextMenuExample(),
      ));
    }
回到顶部