You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 12:06:29