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 栈,稳定可靠,且样式可自定义
相关推荐
ujainu2 小时前
#Flutter + OpenHarmony高保真秒表 App 实现:主副表盘联动、计次记录与主题适配全解析
flutter
向哆哆2 小时前
Flutter × OpenHarmony 跨端实战:打造“智能垃圾分类助手”的快速分类入口模块
flutter·开源·鸿蒙·openharmony
时光慢煮2 小时前
构建跨端驾照学习助手:Flutter × OpenHarmony 的用户信息与驾照状态卡片实现
学习·flutter·开源·openharmony
向哆哆2 小时前
Flutter × OpenHarmony 跨端实战:垃圾分类应用顶部横幅组件的设计与实现
flutter·鸿蒙·openharmony·开源鸿蒙
微祎_2 小时前
Flutter for OpenHarmony:构建一个专业级 Flutter 番茄钟,深入解析状态机、定时器管理与专注力工具设计
开发语言·javascript·flutter
一起养小猫2 小时前
Flutter for OpenHarmony多媒体功能开发完全指南
数码相机·flutter
嘴贱欠吻!3 小时前
Flutter鸿蒙开发指南(八):获取轮播图数据
flutter
晚霞的不甘3 小时前
Flutter for OpenHarmony《智慧字典》英语学习模块代码深度解析:从数据模型到交互体验
前端·学习·flutter·搜索引擎·前端框架·交互
子春一3 小时前
Flutter for OpenHarmony:构建一个优雅的 Flutter 每日一句应用,深入解析状态管理、日期驱动内容与 Material 3 交互动效
javascript·flutter·交互