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 栈,稳定可靠,且样式可自定义
相关推荐
GitLqr14 小时前
玩转 Flutter 中的 Stack 与 Positioned:解决 UI 重叠问题的实战指南
flutter·面试·全栈
唔661 天前
flutter web iOS 在浏览器加载中文慢的问题
前端·flutter·ios
恋猫de小郭1 天前
Jetpack Compose 8 月版正式发布,核心模块 1.12
android·前端·flutter
梦想的颜色2 天前
AI 时代小白 VibeCoding 做 APP:UniApp(含 Uni‑X)、Flutter 与 React Native+Expo全维度技术选型对比指南
flutter·react native·app·uniapp·vibecoding·unippx·app产品
坚果的博客2 天前
Flutter-OH 3.44.9-dev 悄然上线|首个 OpenHarmony Canary 预览版上线
flutter
大龄秃头程序员2 天前
Flutter 动画随笔:业务里真正高频的几类控件
flutter
天空之城--2 天前
Android Flutter行业最新动态与实用参考(2026年8月第2周)
android·flutter
恋猫de小郭2 天前
Flutter 3.47 发布,快来看看有什么更新吧
android·前端·flutter
大龄秃头程序员3 天前
一次 Flutter 动画 Demo 的整理与复盘
flutter