是否存在非全屏覆盖且支持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
相关产品推荐
相关产品推荐

