一、问题说明
在 APP 中,常见的做法是使用:
bash
Navigator.push(
context,
PopupRoute(child: LoadingWidget()),
);
来实现加载弹窗(Loading Dialog)。
实际问题:
- 弹窗与 Navigator 栈绑定
- Loading 弹窗占用栈顶,关闭时依赖 Navigator.pop()
- 如果页面跳转或系统回收,栈状态可能被破坏
- 弹窗被误关闭或无法关闭
- 其他弹窗(例如升级弹窗、确认框)调用 Navigator.pop() 时,可能把 Loading 弹窗直接关闭
- _isShowing 或标识状态与栈状态不同步,导致 Loading 一直显示,用户看到"永久加载"
- 多请求同时显示 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 栈,稳定可靠,且样式可自定义