Flutter中PopupMenuButton自带点击遮罩层如何移除?
问题背景
使用Flutter的PopupMenuButton实现下拉菜单时,存在两个不符合预期的默认行为:
- 组件会自动扩展点击热区,默认按Material规范的最小可点击尺寸生成点击层,超出实际图标范围的区域也会拦截点击事件
- 点击、鼠标悬停时会显示默认的灰色高亮遮罩,查阅组件公开属性和主题配置未找到直接关闭的入口
问题效果参考(图中明显的灰色块就是默认高亮遮罩+多余热区):
原有实现代码:
_notificationBell(BuildContext context) { return Theme( data: Theme.of(context).copyWith(), child: PopupMenuButton<String>( // 菜单项选中回调 onSelected: (String item) {}, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: Stack( children: [ SvgPicture.asset( "assets/bell.svg", semanticsLabel: 'Bell icon', width: 26, height: 26, color: Theme.of(context).hintColor, ), if (userProviderWatch(context).numberOfUnreadBellNotifications() != 0) Positioned( left: 3, top: 3, child: Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.red, ), ), ), ], ), enableFeedback: false, tooltip: lang.bellNotificationsTitle, itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[ PopupMenuItem<String>( enabled: true, value: "ttt", child: ModalBellNotifications(), ), ], ), ); }
可行修复方案
方案1:直接配置PopupMenuButton属性关闭默认效果
灰色遮罩、多余点击热区都是PopupMenuButton内置InkWell组件的默认行为,无需修改源码,新增4个属性即可完全移除:
splashFactory: NoSplash.splashFactory:禁用点击水波纹效果highlightColor: Colors.transparent:设置点击高亮色为完全透明hoverColor: Colors.transparent:设置鼠标悬停色为完全透明materialTapTargetSize: MaterialTapTargetSize.shrinkWrap:将点击热区收缩到和child实际尺寸一致,不再向外扩展多余的点击拦截层
修改后的代码片段:
_notificationBell(BuildContext context) { return PopupMenuButton<String>( onSelected: (String item) {}, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), // 新增以下配置移除默认效果 splashFactory: NoSplash.splashFactory, highlightColor: Colors.transparent, hoverColor: Colors.transparent, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, child: Stack( children: [ SvgPicture.asset( "assets/bell.svg", semanticsLabel: 'Bell icon', width: 26, height: 26, color: Theme.of(context).hintColor, ), if (userProviderWatch(context).numberOfUnreadBellNotifications() != 0) Positioned( left: 3, top: 3, child: Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.red, ), ), ), ], ), enableFeedback: false, tooltip: lang.bellNotificationsTitle, itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[ PopupMenuItem<String>( enabled: true, value: "ttt", child: ModalBellNotifications(), ), ], ); }
如果需要全局统一移除所有PopupMenuButton的这类效果,可以在应用根主题的popupMenuTheme项中统一配置上述属性,无需每个组件单独添加。
方案2:完全自定义触发组件,手动调用弹出方法
如果后续需要添加更多自定义交互(比如长按弹出、点击自定义动画等),可以弃用PopupMenuButton的内置触发层,自行编写展示组件,点击时手动调用showMenu方法弹出菜单。这种方式完全没有默认多余效果,点击热区完全由自定义组件控制:
_notificationBell(BuildContext context) { // 定义全局key获取触发组件位置,用于菜单定位 final buttonKey = GlobalKey(); return GestureDetector( key: buttonKey, onTap: () async { // 获取触发组件的屏幕坐标 final renderBox = buttonKey.currentContext!.findRenderObject() as RenderBox; final offset = renderBox.localToGlobal(Offset.zero); // 手动弹出菜单 final selected = await showMenu<String>( context: context, position: RelativeRect.fromLTRB( offset.dx, offset.dy + renderBox.size.height + 8, // 菜单在图标下方8px位置弹出 offset.dx + renderBox.size.width, offset.dy, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), items: <PopupMenuEntry<String>>[ PopupMenuItem<String>( enabled: true, value: "ttt", child: ModalBellNotifications(), ), ], ); // 处理菜单项选中逻辑 if(selected != null) { // 原onSelected逻辑写在此处 } }, child: Stack( children: [ SvgPicture.asset( "assets/bell.svg", semanticsLabel: 'Bell icon', width: 26, height: 26, color: Theme.of(context).hintColor, ), if (userProviderWatch(context).numberOfUnreadBellNotifications() != 0) Positioned( left: 3, top: 3, child: Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.red, ), ), ), ], ), ); }
内容的提问来源于stack exchange,提问作者Oliver Dixon
相关产品推荐
相关产品推荐

