Flutter中点击AppBar的PopupMenuItem时showDialog不显示的问题
问题:AppBar选项菜单点击无法弹出弹窗
我在AppBar中设置了选项菜单,点击按钮时需要显示弹窗,但尝试多种方案后均无效——已搜索约2小时,试过提取Scaffold组件、用MaterialApp包裹、延迟显示弹窗以及使用WidgetsBinding?.instance?.addPostFrameCallback等方法,都没解决。
以下是我的代码:
class PricingPage extends StatefulWidget{ const PricingPage({Key? key}) : super(key: key); @override _PricingPageState createState() => _PricingPageState(); } class _PricingPageState extends State<PricingPage> with TickerProviderStateMixin { @override Widget build(BuildContext context) { return MaterialApp( home: Builder( builder: (context) => const PricingPageBody(), ) ); } } class PricingPageBody extends StatefulWidget { const PricingPageBody({Key? key}) : super(key: key); @override State<PricingPageBody> createState() => _PricingPageBodyState(); } class _PricingPageBodyState extends State<PricingPageBody> with TickerProviderStateMixin { late TabController _tabController; late PageController pageController; final backendService = Get.put(PricingController()); double undercover = 0.0; double vip = 0.0; double rooftop = 0.0; @override void initState(){ super.initState(); _tabController = TabController(length: 12, vsync: this, initialIndex: DateTime.now().month - 1)..addListener(() { }); pageController = PageController(initialPage: DateTime.now().month - 1); //PricingController().resetAllPrices(22.50, 28.50, 18.50); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( appBar: AppBar( title: const Text('Pricing'), backgroundColor: Colors.transparent, actions: [ Obx(() => Container( margin: const EdgeInsets.symmetric(horizontal: 15), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(backendService.selectedDays.value.toList().length.toString(), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20 ), ), PricingOptionsMenu( selectedDaysLength:backendService.selectedDays.value.toList().length, onUpdateAll: () { }, onDeselectAll: () { backendService.deselectAll(); }, onUpdateSelected: () { }, onUpdateMonth: () { WidgetsBinding?.instance?.addPostFrameCallback((timeStamp) { showDialog(context: context, builder: (BuildContext context) => PricingDialog( dialogTitle: 'Update Prices', prices: (undercover,vip,rooftop) { backendService.updateMonth(undercover, vip, rooftop); }) ); }); }, ), ], ), ) ], ), ), ); } }
解决方法
1. 核心问题分析
你的代码存在多层MaterialApp嵌套:PricingPage返回了一个MaterialApp,而通常整个应用只需要一个顶层的MaterialApp(一般放在main函数中)。每个MaterialApp会创建独立的导航栈(Navigator),导致showDialog使用的context无法找到正确的导航上下文,弹窗无法弹出。
2. 具体修改步骤
步骤1:移除PricingPage中的MaterialApp
将PricingPage的build方法改为直接返回PricingPageBody,避免嵌套导航栈:
class _PricingPageState extends State<PricingPage> with TickerProviderStateMixin { @override Widget build(BuildContext context) { return const PricingPageBody(); } }
步骤2:简化弹窗调用逻辑
去掉不必要的WidgetsBinding回调,直接使用当前context调用showDialog,同时确保弹窗有正确的关闭逻辑:
onUpdateMonth: () { showDialog( context: context, builder: (BuildContext dialogContext) => PricingDialog( dialogTitle: 'Update Prices', prices: (undercover,vip,rooftop) { backendService.updateMonth(undercover, vip, rooftop); Navigator.of(dialogContext).pop(); // 弹窗操作完成后关闭 } ) ); },
步骤3:确保PricingDialog正确实现
检查PricingDialog是否是合法的Dialog组件,示例实现如下:
class PricingDialog extends StatelessWidget { final String dialogTitle; final Function(double, double, double) prices; const PricingDialog({Key? key, required this.dialogTitle, required this.prices}) : super(key: key); // 用于获取输入值的控制器 final _undercoverCtrl = TextEditingController(); final _vipCtrl = TextEditingController(); final _rooftopCtrl = TextEditingController(); @override Widget build(BuildContext context) { return AlertDialog( title: Text(dialogTitle), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _undercoverCtrl, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Undercover Price'), ), TextField( controller: _vipCtrl, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'VIP Price'), ), TextField( controller: _rooftopCtrl, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Rooftop Price'), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), TextButton( onPressed: () { // 解析输入值并回调 final undercover = double.tryParse(_undercoverCtrl.text) ?? 0.0; final vip = double.tryParse(_vipCtrl.text) ?? 0.0; final rooftop = double.tryParse(_rooftopCtrl.text) ?? 0.0; prices(undercover, vip, rooftop); }, child: const Text('Confirm'), ), ], ); } }
3. 关键注意点
- 整个应用只保留一个顶层
MaterialApp,确保导航上下文统一。 showDialog必须使用能访问到Navigator的context,避免使用MaterialApp内部的上下文。- 弹窗组件需包含明确的关闭逻辑(如
Navigator.pop()),避免出现无法关闭的情况。
内容的提问来源于stack exchange,提问作者Danish Ajaib
相关产品推荐
相关产品推荐

