You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中showMenu实现长按分类图标弹出横向菜单问题

解决Flutter长按弹出横向菜单的问题

showMenu 本身是基于纵向列表实现的,无法直接修改为横向排列,得通过自定义弹窗来实现需求。以下是两种可行的实现方案:

方案一:用showDialog搭配横向布局

通过showDialog创建自定义弹窗,内部用Row横向排列菜单项,同时调整弹窗的位置和样式匹配设计需求:

import 'package:flutter/material.dart';

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

  @override
  State<HorizontalMenuDemo> createState() => _HorizontalMenuDemoState();
}

class _HorizontalMenuDemoState extends State<HorizontalMenuDemo> {
  final GlobalKey _iconKey = GlobalKey();

  void _showHorizontalMenu() async {
    // 获取长按图标的位置与尺寸信息
    final RenderBox renderBox = _iconKey.currentContext!.findRenderObject() as RenderBox;
    final offset = renderBox.localToGlobal(Offset.zero);
    final size = renderBox.size;

    await showDialog(
      context: context,
      barrierColor: Colors.transparent,
      builder: (context) {
        return Stack(
          children: [
            // 点击空白区域关闭弹窗
            GestureDetector(
              onTap: () => Navigator.pop(context),
              child: Container(color: Colors.transparent),
            ),
            Positioned(
              left: offset.dx,
              top: offset.dy + size.height + 8, // 图标下方8px处弹出
              child: Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(8),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.grey.withOpacity(0.3),
                      blurRadius: 4,
                      offset: const Offset(0, 2),
                    ),
                  ],
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    _buildMenuItem(Icons.edit, "编辑"),
                    const VerticalDivider(width: 1, color: Colors.grey),
                    _buildMenuItem(Icons.delete, "删除"),
                    const VerticalDivider(width: 1, color: Colors.grey),
                    _buildMenuItem(Icons.share, "分享"),
                  ],
                ),
              ),
            ),
          ],
        );
      },
    );
  }

  Widget _buildMenuItem(IconData icon, String text) {
    return GestureDetector(
      onTap: () {
        Navigator.pop(context);
        // 替换为实际的菜单项点击逻辑
        print("触发$text操作");
      },
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, size: 20, color: Colors.black87),
            const SizedBox(height: 4),
            Text(text, style: const TextStyle(fontSize: 12)),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("横向菜单示例")),
      body: Center(
        child: GestureDetector(
          key: _iconKey,
          onLongPress: _showHorizontalMenu,
          child: const Icon(Icons.category, size: 40),
        ),
      ),
    );
  }
}

方案二:用OverlayEntry实现更灵活的弹窗

如果需要更精细的控制(比如自定义弹窗动画、跟随页面滚动等),可以用OverlayEntry实现:

void _showHorizontalMenuWithOverlay() {
  final RenderBox renderBox = _iconKey.currentContext!.findRenderObject() as RenderBox;
  final offset = renderBox.localToGlobal(Offset.zero);
  final size = renderBox.size;

  OverlayEntry overlayEntry = OverlayEntry(
    builder: (context) => Stack(
      children: [
        GestureDetector(
          onTap: () => overlayEntry.remove(),
          child: Container(color: Colors.transparent),
        ),
        Positioned(
          left: offset.dx,
          top: offset.dy + size.height + 8,
          child: Material(
            elevation: 4,
            borderRadius: BorderRadius.circular(8),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                _buildMenuItem(Icons.edit, "编辑"),
                const VerticalDivider(width: 1),
                _buildMenuItem(Icons.delete, "删除"),
                const VerticalDivider(width: 1),
                _buildMenuItem(Icons.share, "分享"),
              ],
            ),
          ),
        ),
      ],
    ),
  );

  Overlay.of(context).insert(overlayEntry);
}

核心注意点

  • 用GlobalKey获取长按组件的位置,确保弹窗在正确的坐标弹出
  • 外层添加透明GestureDetector,实现点击空白区域关闭弹窗的交互
  • 通过Row横向排列菜单项,可根据需求调整分割线、间距、图标文字样式

内容的提问来源于stack exchange,提问作者anhdhq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 23:24:56