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

是否存在非全屏覆盖且支持onTap的Flutter Cupertino上下文菜单?

实现非全屏覆盖且支持onTap的Cupertino风格上下文菜单

原生CupertinoContextMenu确实是全屏覆盖模式,且仅支持onPressed回调。要实现你需要的非全屏菜单并支持onTap,可以通过组合Flutter的Cupertino组件自定义实现,以下是两种可行方案:


方案1:自定义依附式菜单(跟随触发组件位置)

通过OverlayEntry和CupertinoPopupSurface构建依附于触发组件的非全屏菜单,每个菜单项可直接绑定onTap事件:

import 'package:flutter/cupertino.dart';

class CustomCupertinoMenu extends StatefulWidget {
  final Widget child;
  final List<Widget> menuItems;

  const CustomCupertinoMenu({
    super.key,
    required this.child,
    required this.menuItems,
  });

  @override
  State<CustomCupertinoMenu> createState() => _CustomCupertinoMenuState();
}

class _CustomCupertinoMenuState extends State<CustomCupertinoMenu> {
  OverlayEntry? _overlayEntry;
  final GlobalKey _triggerKey = GlobalKey();

  void _showMenu() {
    final renderBox = _triggerKey.currentContext?.findRenderObject() as RenderBox;
    final triggerPos = renderBox.localToGlobal(Offset.zero);
    final triggerSize = renderBox.size;

    _overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        left: triggerPos.dx,
        top: triggerPos.dy + triggerSize.height + 8,
        child: CupertinoPopupSurface(
          child: SizedBox(
            width: triggerSize.width,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: widget.menuItems.map((item) {
                return GestureDetector(
                  onTap: () {
                    _hideMenu();
                    // 此处绑定菜单项的tap逻辑
                  },
                  child: item,
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );

    Overlay.of(context).insert(_overlayEntry!);
  }

  void _hideMenu() {
    _overlayEntry?.remove();
    _overlayEntry = null;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      key: _triggerKey,
      onTap: _showMenu,
      child: widget.child,
    );
  }
}

// 使用示例
class MenuDemo extends StatelessWidget {
  const MenuDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        child: Center(
          child: CustomCupertinoMenu(
            child: const CupertinoButton(
              child: Text('点击唤出菜单'),
              onPressed: null,
            ),
            menuItems: [
              const CupertinoActionSheetAction(
                child: Text('复制'),
                onPressed: () {},
              ),
              const CupertinoActionSheetAction(
                child: Text('分享'),
                onPressed: () {},
              ),
              const CupertinoActionSheetAction(
                child: Text('删除'),
                isDestructiveAction: true,
                onPressed: () {},
              ),
            ],
          ),
        ),
      ),
    );
  }
}

方案2:自定义弹窗式菜单(固定位置)

如果不需要菜单跟随触发组件,可通过showCupertinoModalPopup创建非全屏弹窗,直接给菜单项绑定onTap:

void showCustomCupertinoMenu(BuildContext context) {
  showCupertinoModalPopup(
    context: context,
    builder: (context) => CupertinoPopupSurface(
      child: SizedBox(
        width: MediaQuery.of(context).size.width * 0.8,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            GestureDetector(
              onTap: () {
                Navigator.pop(context);
                // 处理复制逻辑
              },
              child: const Padding(
                padding: EdgeInsets.symmetric(vertical: 16, horizontal: 24),
                child: Text('复制', style: TextStyle(fontSize: 16)),
              ),
            ),
            const Divider(height: 1),
            GestureDetector(
              onTap: () {
                Navigator.pop(context);
                // 处理分享逻辑
              },
              child: const Padding(
                padding: EdgeInsets.symmetric(vertical: 16, horizontal: 24),
                child: Text('分享', style: TextStyle(fontSize: 16)),
              ),
            ),
            const Divider(height: 1),
            GestureDetector(
              onTap: () {
                Navigator.pop(context);
                // 处理删除逻辑
              },
              child: const Padding(
                padding: EdgeInsets.symmetric(vertical: 16, horizontal: 24),
                child: Text('删除', style: TextStyle(color: CupertinoColors.destructiveRed, fontSize: 16)),
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

两种方案都能实现非全屏的Cupertino风格菜单,且完全支持onTap事件。若需菜单跟随触发组件位置,选方案1;若只需固定位置弹窗,方案2更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:30:57