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

如何在Flutter iOS应用栏实现单点触发的Cupertino风格三点弹窗菜单

实现iOS风格单点触发的应用栏三点弹窗菜单(无Material包)

方案一:使用CupertinoActionSheet + showCupertinoModalPopup

这是最贴合iOS原生弹窗样式的方案,完全基于Cupertino组件,支持单点触发,无需依赖Material包。

代码示例

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  void _showPopupMenu(BuildContext context) {
    showCupertinoModalPopup(
      context: context,
      builder: (BuildContext context) => CupertinoActionSheet(
        title: const Text('操作菜单'),
        message: const Text('选择你要执行的操作'),
        actions: <Widget>[
          CupertinoActionSheetAction(
            onPressed: () {
              Navigator.pop(context);
              // 执行操作1
            },
            child: const Text('操作1'),
          ),
          CupertinoActionSheetAction(
            onPressed: () {
              Navigator.pop(context);
              // 执行操作2
            },
            child: const Text('操作2'),
          ),
        ],
        cancelButton: CupertinoActionSheetAction(
          isDefaultAction: true,
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('取消'),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: const Text('iOS风格菜单'),
        trailing: CupertinoButton(
          padding: EdgeInsets.zero,
          child: const Icon(CupertinoIcons.ellipsis_circle),
          onPressed: () => _showPopupMenu(context),
        ),
      ),
      child: const Center(child: Text('主页面')),
    );
  }
}

说明

  • 点击应用栏右侧的三点图标直接触发弹窗,满足单点触发需求
  • CupertinoActionSheet完全匹配iOS系统弹窗样式,包含标题、操作选项和取消按钮
  • 所有组件均来自flutter/cupertino.dart,无Material依赖

方案二:自定义锚点式弹出菜单(类似ContextMenu位置弹出)

如果需要菜单从三点按钮位置弹出(而非底部全屏弹窗),可以通过OverlayEntry自定义弹窗,结合CupertinoPopupSurface实现iOS风格的圆角弹窗。

代码示例

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      home: HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  OverlayEntry? _overlayEntry;

  void _showAnchorMenu(BuildContext context) {
    final renderBox = context.findRenderObject() as RenderBox;
    final offset = renderBox.localToGlobal(Offset.zero);
    final size = renderBox.size;

    _overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        left: offset.dx + size.width - 120,
        top: offset.dy + size.height + 8,
        child: CupertinoPopupSurface(
          child: SizedBox(
            width: 120,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                CupertinoButton(
                  padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
                  onPressed: () {
                    _overlayEntry?.remove();
                    setState(() => _overlayEntry = null);
                    // 执行操作1
                  },
                  child: const Align(
                    alignment: Alignment.centerLeft,
                    child: Text('操作1'),
                  ),
                ),
                const Divider(height: 1, color: CupertinoColors.systemGrey4),
                CupertinoButton(
                  padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
                  onPressed: () {
                    _overlayEntry?.remove();
                    setState(() => _overlayEntry = null);
                    // 执行操作2
                  },
                  child: const Align(
                    alignment: Alignment.centerLeft,
                    child: Text('操作2'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );

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

  @override
  void dispose() {
    _overlayEntry?.remove();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: const Text('锚点式菜单'),
        trailing: CupertinoButton(
          padding: EdgeInsets.zero,
          child: const Icon(CupertinoIcons.ellipsis_circle),
          onPressed: () {
            if (_overlayEntry != null) {
              _overlayEntry?.remove();
              setState(() => _overlayEntry = null);
            } else {
              _showAnchorMenu(context);
            }
          },
        ),
      ),
      child: const Center(child: Text('主页面')),
    );
  }
}

说明

  • 菜单从三点按钮右上角弹出,更接近CupertinoContextMenu的触发位置
  • 使用CupertinoPopupSurface保证弹窗的iOS风格圆角和阴影
  • 点击菜单选项或再次点击三点按钮均可关闭菜单,交互流畅

关于你之前尝试的方案说明

  1. CupertinoContextMenu本身设计为长按触发,内部手势逻辑会覆盖外部的GestureDetector,所以包裹后无效
  2. 若你之前用的ModalSheet样式不符合需求,方案一的CupertinoActionSheet是专门为iOS设计的操作弹窗,样式更贴合系统规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 07:53:32