Flutter Web中PopupMenuButton触发按钮hover样式无法修改
问题根源
- 按钮处于禁用状态:你给TextButton设置了
onPressed: null,这会让按钮进入MaterialState.disabled状态,Flutter中禁用的按钮不会响应hover、pressed等交互状态,所以你的hover判断代码永远不会执行。 - 样式作用对象错误:
MenuButtonThemeData是给MenuButton类型组件(比如PopupMenuButton本身)设置样式的,而你把它应用到了子组件TextButton上,TextButton属于TextButtonTheme的作用范围,所以样式不会生效。 - 默认背景色未移除:TextButton默认带有hover状态的背景色,需要显式设置为透明来移除。
解决方案
下面提供两种可行的修改方案:
方案一:直接使用PopupMenuButton自带样式
不用自定义TextButton,直接给PopupMenuButton设置样式,这样可以直接利用MenuButtonThemeData或单独设置style:
final ThemeData lightTheme = ThemeData( menuButtonTheme: MenuButtonThemeData(style: ButtonStyle( foregroundColor: MaterialStateProperty.resolveWith<Color?>( (Set<MaterialState> states) { if (states.contains(MaterialState.hovered)) { print("hover triggered!"); return Colors.red; } return Colors.blue; }, ), backgroundColor: MaterialStateProperty.all(Colors.transparent), overlayColor: MaterialStateProperty.all(Colors.transparent), // 移除交互水波纹 )) ); class AppScreen extends StatelessWidget { final Widget? child; AppScreen({Key? key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('AppBar Demo'), actions: <Widget>[ PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem<int>( value: 0, child: Text("My Emails"), ), PopupMenuItem<int>( value: 1, child: Text("Sign Out"), ), ], onSelected: (item) {}, // 自定义触发按钮的布局 child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.account_circle), SizedBox(width: 8), Text('Maya Amor'), ], ), ), // 直接使用Theme中的样式 style: Theme.of(context).menuButtonTheme.style, ) ], ), body: child, ); } }
方案二:修复自定义TextButton的配置
如果一定要用TextButton作为触发按钮,需要让按钮处于可交互状态,并直接给TextButton设置样式:
class AppScreen extends StatelessWidget { final Widget? child; AppScreen({Key? key, this.child}) : super(key: key); final GlobalKey _popupMenuKey = GlobalKey(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('AppBar Demo'), actions: <Widget>[ PopupMenuButton( key: _popupMenuKey, itemBuilder: (context) => [ PopupMenuItem<int>( value: 0, child: Text("My Emails"), ), PopupMenuItem<int>( value: 1, child: Text("Sign Out"), ), ], onSelected: (item) {}, child: TextButton.icon( style: ButtonStyle( foregroundColor: MaterialStateProperty.resolveWith<Color?>( (Set<MaterialState> states) { if (states.contains(MaterialState.hovered)) { print("hover triggered!"); return Colors.red; } return Colors.blue; }, ), backgroundColor: MaterialStateProperty.all(Colors.transparent), overlayColor: MaterialStateProperty.all(Colors.transparent), ), icon: Icon(Icons.account_circle), label: Text('Maya Amor'), // 手动触发菜单弹出,避免按钮禁用 onPressed: () { final RenderBox button = _popupMenuKey.currentContext!.findRenderObject() as RenderBox; final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromPoints( button.localToGlobal(Offset.zero, ancestor: overlay), button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay), ), Offset.zero & overlay.size, ); showMenu<int>( context: context, position: position, items: [ PopupMenuItem<int>(value: 0, child: Text("My Emails")), PopupMenuItem<int>(value: 1, child: Text("Sign Out")), ], ).then((value) { if (value != null) { // 处理选中逻辑 } }); }, ), ) ], ), body: child, ); } }
关键要点
- 永远不要给需要响应hover的按钮设置
onPressed: null,禁用状态会屏蔽所有交互状态。 - 不同类型的按钮组件对应不同的ThemeData(TextButton对应TextButtonThemeData,MenuButton对应MenuButtonThemeData),不要混用。
- 移除hover背景色需要同时设置
backgroundColor和overlayColor为透明,后者是处理点击时的水波纹效果。
内容的提问来源于stack exchange,提问作者Maya Amor
相关产品推荐
相关产品推荐

