Flutter自定义弹出菜单插件custom_pop_up_menu_fork的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter自定义弹出菜单插件custom_pop_up_menu_fork的使用

特性

  • 手势:点击或长按
  • 可自定义菜单:可以设置任何样式的菜单,常见的样式可以在示例代码中查看,只需实现menuBuilder
  • 预设两种样式:gridview 和 listview,支持深色模式和浅色模式
  • 自动计算菜单的位置,也支持手动调整
  • 修复了原始库中无法隐藏菜单的错误

Demo

使用此插件,你可以自由地构建类似于微信的弹出菜单。

预设样式

menuBuilder: () {
  return GridViewPopMenuLight(menuItems: menuItems, dataObj: message.content, controller: menuController);
},
menuBuilder: () => ListViewPopMenuLight(menuItems: menuItems, dataObj: "添加按钮", controller: _controller),

快速使用API

推荐使用此API:

QuickPopUpMenu({
  Key? key,
  required this.child,
  required this.menuItems,
  required this.dataObj,
  this.darkMode,
  this.pressType,
  this.useGridView,
  this.showArrow,
})

这是一个简单的封装:

Widget build(BuildContext context) {
  return CustomPopupMenu(
    child: child,
    menuBuilder: () {
      if(useGridView ?? false) {
        if(darkMode ?? false) {
          return GridViewPopMenu(menuItems: menuItems, dataObj: dataObj, controller: menuController);
        } else {
          return GridViewPopMenuLight(menuItems: menuItems, dataObj: dataObj, controller: menuController);
        }
      } else {
        if(darkMode ?? false) {
          return ListViewPopMenu(menuItems: menuItems, dataObj: dataObj, controller: menuController);
        } else {
          return ListViewPopMenuLight(menuItems: menuItems, dataObj: dataObj, controller: menuController);
        }
      }
    },
    controller: menuController,
    barrierColor: Colors.transparent,
    showArrow: showArrow ?? false,
    pressType: pressType ?? PressType.singleClick,
    verticalMargin: 0,
  );
}

使用示例

QuickPopUpMenu(
  child: Text(message.content),
  pressType: PressType.longPress,
  menuItems: menuItems,
  useGridView: true,
  dataObj: message.content,
)

原始库中的Bug修复

当在listview/gridview/scrollview中使用popmenu时,调用controller.hideMenu()无法关闭菜单,因为控制器在滚动/重用项目时会改变。

因此,我们不得不通过不同的控制器传递事件:

_updateView() {
  if(widget.controller != null && widget.controller != _controller) {
    if(!widget.controller!.hasListeners) {
      widget.controller!.addListener(() {
        _controller!.toggleMenu();
      });
    }
  }

  bool menuIsShowing = _controller?.menuIsShowing ?? false;
  widget.menuOnChange?.call(menuIsShowing);
  if (menuIsShowing) {
    _showMenu();
  } else {
    _hideMenu();
  }
}

示例代码

import 'package:custom_pop_up_menu_fork/custom_pop_up_menu.dart';
import 'package:flutter/material.dart';

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

class ChatModel {
  String content;
  bool isMe;

  ChatModel(this.content, {this.isMe = false});
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CustomPopupMenu',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<ChatModel> messages;
  late List<PopMenuItemModel> menuItems;
  CustomPopupMenuController _controller = CustomPopupMenuController();

  [@override](/user/override)
  void initState() {
    messages = [
      ChatModel('在吗?'),
      ChatModel('咋了?找我有事吗?', isMe: true),
      ChatModel('没啥就像看看你在不在'),
      ChatModel('到底啥事你说啊,我还在工作呢', isMe: true),
      ChatModel('?', isMe: true),
      ChatModel('下面开始介绍Flutter'),
      ChatModel(
        'Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。',
      ),
      ChatModel('就这???', isMe: true),
      ChatModel('在吗?'),
      ChatModel('咋了?找我有事吗?', isMe: true),
      ChatModel('没啥就像看看你在不在'),
      ChatModel('到底啥事你说啊,我还在工作呢', isMe: true),
      ChatModel('?', isMe: true),
      ChatModel('下面开始介绍Flutter'),
      ChatModel(
        'Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。',
      ),
      ChatModel('就这???', isMe: true),
    ];
    menuItems = [
      PopMenuItemModel(title: '发起群聊', icon: Icons.chat_bubble, callback: (data) { debugPrint("data: $data"); }),
      PopMenuItemModel(title: '添加朋友', icon: Icons.group_add, callback: (data) { debugPrint("data: $data"); }),
      PopMenuItemModel(title: '扫一扫', icon: Icons.settings_overscan, callback: (data) { debugPrint("data: $data"); }),
    ];
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('CustomPopupMenu'),
        actions: <Widget>[
          CustomPopupMenu(
            child: Container(
              child: Icon(Icons.add_circle_outline, color: Colors.white),
              padding: EdgeInsets.all(20),
            ),
            menuBuilder: () => ListViewPopMenu(menuItems: menuItems, dataObj: "添加按钮", controller: _controller),
            pressType: PressType.singleClick,
            verticalMargin: -10,
            controller: _controller,
          ),
        ],
      ),
      body: LayoutBuilder(
        builder: (context, constraint) {
          return SingleChildScrollView(
            physics: AlwaysScrollableScrollPhysics(),
            child: ConstrainedBox(
              constraints: BoxConstraints(minHeight: constraint.maxHeight),
              child: Column(
                children: messages
                    .map(
                      (message) => MessageContent(
                        message,
                      ),
                    )
                    .toList(),
              ),
            ),
          );
        },
      ),
    );
  }
}

class MessageContent extends StatelessWidget {
  MessageContent(this.message);

  final ChatModel message;
  List<PopMenuItemModel> menuItems = [
    PopMenuItemModel(title: '复制', icon: Icons.content_copy, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '转发', icon: Icons.send, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '收藏', icon: Icons.collections, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '删除', icon: Icons.delete, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '多选', icon: Icons.playlist_add_check, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '引用', icon: Icons.format_quote, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '提醒', icon: Icons.add_alert, callback: (data) { debugPrint("data: $data"); }),
    PopMenuItemModel(title: '搜一搜', icon: Icons.search, callback: (data) { debugPrint("data: $data"); }),
  ];

  Widget _buildLongPressMenu(CustomPopupMenuController menuController) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(5),
      child: Container(
        width: 220,
        color: const Color(0xFF4C4C4C),
        child: GridView.count(
          padding: EdgeInsets.symmetric(horizontal: 5, vertical: 10),
          crossAxisCount: 5,
          crossAxisSpacing: 0,
          mainAxisSpacing: 10,
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          children: menuItems
              .map((item) => GestureDetector(
                    onTap: () {
                      debugPrint("click-->$item.title");
                      menuController.hideMenu();
                    },
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                        Icon(
                          item.icon,
                          size: 20,
                          color: Colors.white,
                        ),
                        Container(
                          margin: EdgeInsets.only(top: 2),
                          child: Text(
                            item.title,
                            style: TextStyle(color: Colors.white, fontSize: 12),
                          ),
                        ),
                      ],
                    ),
                  ))
              .toList(),
        ),
      ),
    );
  }

  Widget _buildAvatar(bool isMe, double size) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(5),
      child: Container(
        color: isMe ? Colors.blueAccent : Colors.pinkAccent,
        width: size,
        height: size,
        child: Icon(
          isMe ? Icons.face : Icons.tag_faces,
          color: Colors.white,
        ),
      ),
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    bool isMe = message.isMe;
    double avatarSize = 40;

    return Container(
      margin: EdgeInsets.all(10),
      child: Row(
        textDirection: isMe ? TextDirection.rtl : TextDirection.ltr,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
            margin: EdgeInsets.only(right: isMe ? 0 : 10, left: isMe ? 10 : 0),
            child: CustomPopupMenu(
              child: _buildAvatar(isMe, avatarSize),
              menuBuilder: () => GestureDetector(
                child: _buildAvatar(isMe, 100),
                onLongPress: () {
                  debugPrint("onLongPress");
                },
                onTap: () {
                  debugPrint("onTap");
                },
              ),
              barrierColor: Colors.transparent,
              pressType: PressType.singleClick,
              arrowColor: isMe ? Colors.blueAccent : Colors.pinkAccent,
              position: PreferredPosition.top,
            ),
          ),
          QuickPopUpMenu(
            child: Container(
              padding: EdgeInsets.all(10),
              constraints: BoxConstraints(maxWidth: 240, minHeight: avatarSize),
              decoration: BoxDecoration(
                color: isMe ? Color(0xff98e165) : Colors.white,
                borderRadius: BorderRadius.circular(3.0),
              ),
              child: Text(message.content),
            ),
            menuBuilder: () => _buildLongPressMenu(menuController),
            controller: menuController,
            barrierColor: Colors.transparent,
            showArrow: false,
            pressType: PressType.longPress,
            menuItems: menuItems,
            useGridView: true,
            dataObj: message.content,
          )
        ],
      ),
    );
  }
}

更多关于Flutter自定义弹出菜单插件custom_pop_up_menu_fork的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义弹出菜单插件custom_pop_up_menu_fork的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用custom_pop_up_menu_fork插件的示例代码。这个插件允许你创建自定义的弹出菜单。假设你已经将custom_pop_up_menu_fork添加到了你的pubspec.yaml文件中,并且已经运行了flutter pub get

1. 添加依赖

首先,确保你的pubspec.yaml文件中包含以下依赖:

dependencies:
  flutter:
    sdk: flutter
  custom_pop_up_menu_fork: ^最新版本号

2. 导入插件

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

import 'package:custom_pop_up_menu_fork/custom_pop_up_menu_fork.dart';

3. 使用插件

下面是一个完整的示例,展示如何在Flutter中使用custom_pop_up_menu_fork插件:

import 'package:flutter/material.dart';
import 'package:custom_pop_up_menu_fork/custom_pop_up_menu_fork.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  // 菜单项点击事件处理函数
  void _onMenuItemClicked(String item) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("You clicked: $item")),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Pop Up Menu Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Long press to show custom pop up menu',
            ),
            SizedBox(height: 20),
            GestureDetector(
              onLongPress: () {
                showMenu(
                  context: context,
                  position: RelativeRect.fromLTRB(50, 100, 50, 0),
                  items: [
                    PopupMenuItem(
                      child: Text('Item 1'),
                      value: 'Item 1',
                    ),
                    PopupMenuItem(
                      child: Text('Item 2'),
                      value: 'Item 2',
                    ),
                    PopupMenuItem(
                      child: Text('Item 3'),
                      value: 'Item 3',
                    ),
                  ],
                  onSelected: _onMenuItemClicked,
                );
              },
              child: Container(
                width: 200,
                height: 50,
                color: Colors.grey.withOpacity(0.5),
                alignment: Alignment.center,
                child: Text(
                  'Long Press Here',
                  style: TextStyle(color: Colors.black),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

解释

  1. 依赖添加:确保在pubspec.yaml文件中添加了custom_pop_up_menu_fork依赖。
  2. 导入插件:在需要使用的Dart文件中导入插件。
  3. 创建菜单
    • 使用GestureDetectoronLongPress事件监听长按操作。
    • 使用showMenu函数显示弹出菜单,并设置菜单项和点击事件处理函数。

这个示例展示了如何在Flutter中使用custom_pop_up_menu_fork插件来创建一个自定义的弹出菜单,并在用户选择菜单项时执行相应的操作。你可以根据需要自定义菜单项的样式和点击事件的处理逻辑。

回到顶部