Flutter:VoidCallback在OnPressed生效但OnTap无效的问题
问题场景
我定义了一个PopupMenu组件:
class PopupMenu extends StatelessWidget { final VoidCallback editTaskCallback; const PopupMenu({ required this.editTaskCallback, super.key, }); @override Widget build(BuildContext context) { return PopupMenuButton( itemBuilder:(context) => [ PopupMenuItem( onTap: null, child: TextButton.icon( onPressed: editTaskCallback, icon: const Icon(Icons.edit), label: const Text('Edit'), ), ), ], ); } }
调用时传入回调:
PopupMenu( editTaskCallback: () {_editTask(context)}, ),
其中_editTask方法用于弹出底部sheet:
void _editTask(BuildContext context) { showModalBottomSheet( isScrollControlled: true, context: context, builder: (context) => SingleChildScrollView( child: Container( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: EditTaskBottom( oldTask: task, ), ), ), ); }
遇到的问题:将editTaskCallback赋值给TextButton的onPressed时,showModalBottomSheet能正常弹出;但赋值给PopupMenuItem的onTap时,回调里的print语句能执行,却无法弹出底部sheet。
原因解析
PopupMenuItem的onTap是在菜单关闭流程中触发的:点击PopupMenuItem后,Flutter会先执行onTap,紧接着调用Navigator.pop关闭菜单。此时showModalBottomSheet依赖的context已经因为菜单关闭而失效,导致无法正确弹出。TextButton的onPressed是在菜单关闭前触发的:点击TextButton时,菜单还处于打开状态,对应的context依然有效,所以showModalBottomSheet能正常工作。
解决方法
方法1:延迟执行回调
通过延迟执行showModalBottomSheet,等待菜单完全关闭后再弹出底部sheet:
void _editTask(BuildContext context) { Future.delayed(const Duration(milliseconds: 100), () { showModalBottomSheet( isScrollControlled: true, context: context, builder: (context) => SingleChildScrollView( child: Container( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: EditTaskBottom( oldTask: task, ), ), ), ); }); }
方法2:使用根导航上下文
获取根导航的context,避免菜单关闭带来的上下文失效问题:
void _editTask(BuildContext context) { showModalBottomSheet( isScrollControlled: true, context: Navigator.of(context, rootNavigator: true), builder: (context) => SingleChildScrollView( child: Container( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: EditTaskBottom( oldTask: task, ), ), ), ); }
方法3:使用PopupMenuButton的onSelected(推荐)
重构组件,利用PopupMenuButton的onSelected回调,该回调会在菜单关闭后触发,无需担心上下文问题:
class PopupMenu extends StatelessWidget { final VoidCallback editTaskCallback; const PopupMenu({ required this.editTaskCallback, super.key, }); @override Widget build(BuildContext context) { return PopupMenuButton( onSelected: (value) { if (value == 'edit') { editTaskCallback(); } }, itemBuilder:(context) => [ const PopupMenuItem( value: 'edit', child: Row( children: [ Icon(Icons.edit), SizedBox(width: 8), Text('Edit'), ], ), ), ], ); } }
内容的提问来源于stack exchange,提问作者Hamou Ouyaba
相关产品推荐
相关产品推荐

