Flutter中如何为多个PopupMenuButton正确使用Global Key?
问题说明
实现鼠标悬停PopupMenuButton时自动弹出菜单,单个按钮用GlobalKey可以实现,但复用同一个GlobalKey会报错——一个GlobalKey只能绑定一个Widget。
解决思路
1. 给每个PopupMenuButton分配独立GlobalKey
不能复用同一个GlobalKey,每个需要悬停触发的PopupMenuButton都要创建专属的GlobalKey。
2. 重构触发方法,支持传入对应GlobalKey
修改openPopUpItem方法,让它接收一个GlobalKey参数,这样每个InkWell的onHover逻辑可以针对对应按钮触发。
3. 封装自定义组件(推荐)
把悬停逻辑和PopupMenuButton封装成自定义组件,避免重复代码,后续直接复用组件即可。
代码实现
方案1:独立GlobalKey+重构触发方法
// 为每个需要的按钮创建独立GlobalKey GlobalKey popUpButtonKeyAgenturen = GlobalKey(); GlobalKey popUpButtonKeyWerben = GlobalKey(); // 重构openPopUpItem,接收GlobalKey参数 void openPopUpItem(GlobalKey key) { GestureDetector? detector; void searchForGestureDetector(BuildContext element) { element.visitChildElements((element) { if (element.widget is GestureDetector) { detector = element.widget as GestureDetector; } else { searchForGestureDetector(element); } }); } final currentContext = key.currentContext; if (currentContext != null) { searchForGestureDetector(currentContext); detector?.onTap?.call(); } } // 使用时传入对应GlobalKey body: ListView( children: [ // ... 其他代码 if (!Responsive.isMobile(context)) InkWell( onHover: (value) { if (value) openPopUpItem(popUpButtonKeyAgenturen); }, onTap: () {}, child: PopupMenuButton( key: popUpButtonKeyAgenturen, tooltip: '', color: Color(0xFF262533), position: PopupMenuPosition.under, child: Text( 'Agenturen & Clubs', style: TextStyle( color: Colors.white, fontSize: 24, fontFamily: 'Poppins', ), ), itemBuilder: (BuildContext context) => <PopupMenuEntry>[ // ... 菜单项 ], ), ), // ... 其他代码 if (!Responsive.isMobile(context)) InkWell( onHover: (value) { if (value) openPopUpItem(popUpButtonKeyWerben); }, onTap: () {}, child: PopupMenuButton( key: popUpButtonKeyWerben, tooltip: '', color: Color(0xFF262533), position: PopupMenuPosition.under, child: Text( 'Werben', style: TextStyle( color: Colors.white, fontSize: 24, fontFamily: 'Poppins', ), ), itemBuilder: (BuildContext context) => <PopupMenuEntry>[ // ... 菜单项 ], ), ), ], )
方案2:自定义HoverPopupMenuButton组件(推荐)
class HoverPopupMenuButton extends StatefulWidget { final Widget child; final List<PopupMenuEntry> Function(BuildContext) itemBuilder; final Color? color; final PopupMenuPosition? position; const HoverPopupMenuButton({ super.key, required this.child, required this.itemBuilder, this.color, this.position, }); @override State<HoverPopupMenuButton> createState() => _HoverPopupMenuButtonState(); } class _HoverPopupMenuButtonState extends State<HoverPopupMenuButton> { final GlobalKey _popupKey = GlobalKey(); void _openMenu() { GestureDetector? detector; void searchDetector(BuildContext element) { element.visitChildElements((element) { if (element.widget is GestureDetector) { detector = element.widget as GestureDetector; } else { searchDetector(element); } }); } final context = _popupKey.currentContext; if (context != null) { searchDetector(context); detector?.onTap?.call(); } } @override Widget build(BuildContext context) { return InkWell( onHover: (isHovering) { if (isHovering) _openMenu(); }, onTap: () {}, child: PopupMenuButton( key: _popupKey, tooltip: '', color: widget.color, position: widget.position, child: widget.child, itemBuilder: widget.itemBuilder, ), ); } }
使用自定义组件
body: ListView( children: [ // ... 其他代码 if (!Responsive.isMobile(context)) HoverPopupMenuButton( color: Color(0xFF262533), position: PopupMenuPosition.under, child: Text( 'Agenturen & Clubs', style: TextStyle( color: Colors.white, fontSize: 24, fontFamily: 'Poppins', ), ), itemBuilder: (BuildContext context) => <PopupMenuEntry>[ const PopupMenuItem(child: ListTile(title: Text('Escortagenturen', style: TextStyle(color: Colors.white)))), const PopupMenuItem(child: ListTile(title: Text('Bordelle', style: TextStyle(color: Colors.white)))), // ... 其他菜单项 ], ), // ... 其他代码 if (!Responsive.isMobile(context)) HoverPopupMenuButton( color: Color(0xFF262533), position: PopupMenuPosition.under, child: Text( 'Werben', style: TextStyle( color: Colors.white, fontSize: 24, fontFamily: 'Poppins', ), ), itemBuilder: (BuildContext context) => <PopupMenuEntry>[ const PopupMenuItem(child: ListTile(title: Text('Werbenformate', style: TextStyle(color: Colors.white)))), const PopupMenuItem(child: ListTile(title: Text('Preise', style: TextStyle(color: Colors.white)))), ], ), ], )
内容的提问来源于stack exchange,提问作者James James
相关产品推荐
相关产品推荐

