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

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

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

简介

通过使用CustomPopupMenu包裹一个Widget,当用户点击或长按该Widget时,会显示一个自定义的弹出菜单。这个插件允许你完全自定义菜单样式,并且可以自动计算菜单的位置,也可以手动调整。

特性

  • 手势:支持点击和长按触发。
  • 完全自定义菜单:该插件不预设任何菜单样式,你可以根据自己的需求进行定制(常见样式见示例代码)。
  • 自动计算位置:支持自动计算菜单出现的位置,也支持手动调整。

示例效果

以下是使用此插件构建的类似微信弹出菜单的效果图:

示例1 示例2 示例3

完整示例代码

import 'package:custom_pop_up_menu/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 ItemModel {
  String title;
  IconData icon;

  ItemModel(this.title, this.icon);
}

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

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

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

  @override
  void initState() {
    super.initState();
    messages = [
      ChatModel('在吗?'),
      ChatModel('咋了?找我有事吗?', isMe: true),
      // ... 更多消息项
    ];
    menuItems = [
      ItemModel('发起群聊', Icons.chat_bubble),
      ItemModel('添加朋友', Icons.group_add),
      ItemModel('扫一扫', Icons.settings_overscan),
    ];
  }

  @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: () => ClipRRect(
              borderRadius: BorderRadius.circular(5),
              child: Container(
                color: const Color(0xFF4C4C4C),
                child: IntrinsicWidth(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: menuItems
                        .map((item) => GestureDetector(
                              behavior: HitTestBehavior.translucent,
                              onTap: () {
                                print("onTap");
                                _controller.hideMenu();
                              },
                              child: Container(
                                height: 40,
                                padding: EdgeInsets.symmetric(horizontal: 20),
                                child: Row(
                                  children: <Widget>[
                                    Icon(item.icon, size: 15, color: Colors.white),
                                    Expanded(
                                      child: Container(
                                        margin: EdgeInsets.only(left: 10),
                                        padding: EdgeInsets.symmetric(vertical: 10),
                                        child: Text(
                                          item.title,
                                          style: TextStyle(color: Colors.white, fontSize: 12),
                                        ),
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                            ))
                        .toList(),
                  ),
                ),
              ),
            ),
            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 {
  final ChatModel message;
  List<ItemModel> menuItems = [
    ItemModel('复制', Icons.content_copy),
    ItemModel('转发', Icons.send),
    // ... 更多项
  ];

  MessageContent(this.message);

  Widget _buildLongPressMenu() {
    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) => 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
  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: () {
                  print("onLongPress");
                },
                onTap: () {
                  print("onTap");
                },
              ),
              barrierColor: Colors.transparent,
              pressType: PressType.singleClick,
              arrowColor: isMe ? Colors.blueAccent : Colors.pinkAccent,
              position: PreferredPosition.top,
            ),
          ),
          CustomPopupMenu(
            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,
            barrierColor: Colors.transparent,
            pressType: PressType.longPress,
          )
        ],
      ),
    );
  }
}

以上代码展示了如何使用custom_pop_up_menu插件创建一个包含上下文菜单的消息列表应用。你可以根据需要进一步修改和扩展这个示例。


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

1 回复

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


当然,以下是如何在Flutter中使用custom_pop_up_menu插件来创建一个自定义弹出菜单的示例代码。

首先,确保你已经在pubspec.yaml文件中添加了custom_pop_up_menu依赖:

dependencies:
  flutter:
    sdk: flutter
  custom_pop_up_menu: ^x.y.z  # 请将x.y.z替换为最新版本号

然后运行flutter pub get来安装依赖。

接下来,是一个完整的示例代码,展示如何使用custom_pop_up_menu来创建一个自定义弹出菜单:

import 'package:flutter/material.dart';
import 'package:custom_pop_up_menu/custom_pop_up_menu.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> {
  final CustomPopupMenuItemBuilder<String> _itemBuilder = (BuildContext context, String item, bool isSelected) {
    return ListTile(
      leading: Icon(Icons.label),
      title: Text(item),
      selected: isSelected,
      onTap: () {
        Navigator.pop(context, item);
      },
    );
  };

  void _showPopupMenu(BuildContext context) {
    showCustomPopupMenu<String>(
      context: context,
      items: ['Option 1', 'Option 2', 'Option 3'],
      itemBuilder: _itemBuilder,
    ).then((result) {
      if (result != null) {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text('You selected: $result'),
        ));
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Popup Menu Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => _showPopupMenu(context),
          child: Text('Show Popup Menu'),
        ),
      ),
    );
  }
}

代码解释:

  1. 依赖导入

    • 导入flutter/material.dartcustom_pop_up_menu/custom_pop_up_menu.dart
  2. 应用入口

    • MyApp类作为应用的入口,定义了主题和主页。
  3. 主页

    • MyHomePage是一个有状态的Widget,包含一个按钮,点击按钮时会显示弹出菜单。
  4. 弹出菜单项构建器

    • _itemBuilder函数定义了菜单项的样式和行为。这里每个菜单项是一个ListTile,包含图标和文本。当用户点击菜单项时,会关闭菜单并返回选中的项。
  5. 显示弹出菜单

    • _showPopupMenu函数使用showCustomPopupMenu方法显示弹出菜单。它接受上下文、菜单项列表和项构建器作为参数。当用户选择一项后,通过then方法处理返回的结果,并显示一个SnackBar。
  6. UI布局

    • build方法中,返回一个包含按钮的Scaffold。按钮的onPressed回调调用_showPopupMenu函数。

这个示例展示了如何使用custom_pop_up_menu插件来创建一个简单的自定义弹出菜单。你可以根据需要进一步自定义菜单项的样式和行为。

回到顶部