Flutter加载弹窗使用问题及解决方案

一、问题说明

在 APP 中,常见的做法是使用:

bash 复制代码
Navigator.push(
  context,
  PopupRoute(child: LoadingWidget()),
);

来实现加载弹窗(Loading Dialog)。
实际问题:

  1. 弹窗与 Navigator 栈绑定
  • Loading 弹窗占用栈顶,关闭时依赖 Navigator.pop()
  • 如果页面跳转或系统回收,栈状态可能被破坏
  1. 弹窗被误关闭或无法关闭
  • 其他弹窗(例如升级弹窗、确认框)调用 Navigator.pop() 时,可能把 Loading 弹窗直接关闭
  • _isShowing 或标识状态与栈状态不同步,导致 Loading 一直显示,用户看到"永久加载"
  1. 多请求同时显示 Loading 弹窗容易冲突
  • 多次 Navigator.push 会叠加在栈顶
  • pop 一个可能影响另一个

结论:
❌ Loading 弹窗不能依赖 Navigator 弹出,否则容易出现弹窗关闭异常、UI 卡死或误关闭其他弹窗的情况。


二、解决方案

1️⃣ 使用 Overlay 管理 Loading 弹窗

  • Overlay 是独立于 Navigator 栈的浮层
  • 可以随时显示或隐藏,不受页面跳转、其他 Dialog 或系统回收影响
  • 可以保证 Loading 弹窗独立、可靠、可复用
    2️⃣ 核心思路
  • 创建 OverlayEntry 来展示 Loading 弹窗
  • 显示时调用 Overlay.of(context).insert(entry)
  • 隐藏时调用 entry.remove()

三、示例代码

bash 复制代码
import 'package:audio_wave/audio_wave.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

///加载弹框(方法名不变,样式完全保留)
class CallStatusDialog {
  static OverlayEntry? _entry;

  ///展示
  static void showProgress(BuildContext context, String content) {
    if (_entry != null) return;

    _entry = OverlayEntry(
      builder: (_) => Stack(
        children: [
          const ModalBarrier(
            dismissible: false,
            color: Colors.black54,
          ),
          Center(
            child: new Padding(
              padding: const EdgeInsets.all(12.0),
              child: new Center(
                child: new SizedBox(
                  width: 120.0,
                  height: 120.0,
                  child: new Container(
                    decoration: ShapeDecoration(
                      color: Color(0xffffffff),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.all(
                          Radius.circular(8.0),
                        ),
                      ),
                    ),
                    child: new Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: <Widget>[
                        AudioWave(
                          height: 32,
                          width: 50,
                          spacing: 2.5,
                          beatRate: Duration(milliseconds: 400),
                          bars: [
                            AudioWaveBar(heightFactor: 0.3, color: Colors.lightBlue.shade50),
                            AudioWaveBar(heightFactor: 0.6, color: Colors.lightBlue.shade100),
                            AudioWaveBar(heightFactor: 0.5, color: Colors.lightBlue.shade200),
                            AudioWaveBar(heightFactor: 0.8, color: Colors.lightBlue.shade300),
                            AudioWaveBar(heightFactor: 0.6, color: Colors.lightBlue.shade400),
                            AudioWaveBar(heightFactor: 1, color: Colors.lightBlue.shade500),
                            AudioWaveBar(heightFactor: 0.6, color: Colors.lightBlue.shade600),
                            AudioWaveBar(heightFactor: 0.4, color: Colors.lightBlue.shade700),
                            AudioWaveBar(heightFactor: 0.5, color: Colors.lightBlue.shade800),
                            AudioWaveBar(heightFactor: 0.2, color: Colors.lightBlue.shade900),

                          ],
                        ),
                        Container(
                          margin: EdgeInsets.only(top: 15),
                          child: Text(content,style: TextStyle(fontSize: 15,color: Colors.black87,fontFamily: 'PingFangBold', decoration: TextDecoration.none,),),
                        )
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );

    Overlay.of(context, rootOverlay: true)!.insert(_entry!);
  }

  ///隐藏
  static void hideProgress(BuildContext context) {
    _entry?.remove();
    _entry = null;
  }
}

四、使用方式

bash 复制代码
// 显示加载弹窗
CallStatusDialog.showProgress(context, "加载中...");

// 隐藏加载弹窗
CallStatusDialog.hideProgress(context);

特点:

  • 弹窗独立于 Navigator 栈,不会被其他页面或弹窗误关闭
  • 支持多次调用,只显示一个实例
  • UI 样式可完全自定义

五、总结

不要用 Navigator 弹出 Loading 弹窗,否则会出现:

  • 弹窗误关闭
  • 永久 loading
  • 页面跳转导致状态异常
  • 推荐使用 Overlay 管理 Loading 弹窗,独立于 Navigator 栈,稳定可靠,且样式可自定义
相关推荐
程序员Ctrl喵18 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难20 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡21 小时前
flutter列表中实现置顶动画
flutter
始持21 小时前
第十二讲 风格与主题统一
前端·flutter
始持21 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持21 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区1 天前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎1 天前
树形选择器组件封装
前端·flutter