解决了什么问题:
- 原生的
showDialog
基于Navigator
弹出和关闭,在具有一定复杂度的业务中无法判断你在pop
谁。我这个方案每个弹窗都有一个对应的实例可以控制,且不会被Navigator
的pop
方法影响。 - 优化了对于Loading业务的弹窗使用,原生弹窗很难在非线性流程下控制关闭弹窗。
样式展示


显示一般类型的Alert
Dart
DialogController alert = DialogController.alert(
title: "Title",
subTitle: "SubTitle",
onCancel: () {
print("alert cancle");
},
run: () async {
// 一些耗时操作
return true;
},
);
alert.show(context);
显示加载类型的Alert
Dart
DialogController loadingAlert = DialogController.loadingAlert();
loadingAlert.showWithTimeout(context, timeout: 10);
// await 其他耗时操作
loadingAlert.close();
实现代码,这里只是提供一个思路,更多样式按照这个规范拓展即可
Dart
import 'dart:async';
import 'package:flutter/material.dart';
class BaseDialog {
BaseDialog(this._barrierDismissible, this._alignment);
/// 点击背景是否关闭弹窗
final bool _barrierDismissible;
final AlignmentGeometry _alignment;
/// 页面状态,用来做动画判断
bool _isCloseState = true;
/// 动画时长
final _milliseconds = 240;
/// 初始化dialog的内容
/// [isClose]用来标识动画的状态
/// [milliseconds]用来标识动画时长
initContentView(Widget Function(BuildContext context, bool isClose, int milliseconds) builder) {
_overlayEntry = OverlayEntry(
builder: (context) {
return Stack(
alignment: _alignment,
children: <Widget>[
// 背景
Positioned.fill(
child: GestureDetector(
onTap: () {
// 点击背景关闭页面
if (_barrierDismissible) close();
},
child: AnimatedOpacity(
opacity: _isCloseState ? 0.0 : 1,
duration: Duration(milliseconds: _milliseconds),
child: Container(
color: Colors.black.withOpacity(0.5),
),
),
),
),
builder(context, _isCloseState, _milliseconds),
],
);
},
);
}
late OverlayEntry _overlayEntry;
bool _isPop = true;
/// 显示弹窗
/// 小等于0不设置超时
void show(BuildContext context, int timeout) async {
//显示弹窗
Overlay.of(context).insert(_overlayEntry);
// 稍微延迟一下,不然动画不动
await Future.delayed(const Duration(milliseconds: 10));
_isCloseState = false;
// 重新build启动动画
_overlayEntry.markNeedsBuild();
_isPop = true;
// 启动计时器,timeout秒后执行关闭操作
if (timeout > 0) {
Future.delayed(Duration(seconds: timeout), () => close());
}
}
/// 关闭弹窗
Future<void> close() async {
if (_isPop) {
_isPop = false;
_isCloseState = true;
// 重新build启动动画
_overlayEntry.markNeedsBuild();
// 等待动画结束后再移除涂层
await Future.delayed(Duration(milliseconds: _milliseconds));
_overlayEntry.remove();
onClose();
}
}
void Function() onClose = (){};
}
class DialogController {
DialogController(this._baseDialog);
final BaseDialog _baseDialog;
/// 关闭弹窗
close() {
_baseDialog.close();
}
/// 显示弹窗
show(BuildContext context) {
_baseDialog.show(context, 0);
}
/// 显示一个默认带超时的弹窗
/// 小等于0不设置超时
void showWithTimeout(BuildContext context, {int timeout = 20}) {
_baseDialog.show(context, timeout);
}
/// 创造一个普通样式的alert弹窗
/// 它显示在屏幕中央,具有一个标题和内容描述文本,
/// [onBarrierTap]当点击背景时触发
factory DialogController.alert({
required String title,
required String subTitle,
bool barrierDismissible = true,
Future<bool> Function()? run,
void Function()? onCancel,
}) {
final dialog = BaseDialog(
barrierDismissible,
AlignmentDirectional.center,
);
if (onCancel != null) {
dialog.onClose = onCancel;
}
dialog.initContentView((context, isClose, int milliseconds) {
return AnimatedOpacity(
opacity: isClose ? 0.0 : 1,
duration: Duration(milliseconds: milliseconds),
child: AlertDialog(
title: Text(title),
content: Text(subTitle),
actions: [
FilledButton.tonal(
onPressed: () {
dialog.close();
},
child: const Text("取消"),
),
FilledButton(
onPressed: () async {
if (run != null) {
final r = await run();
if (r) dialog.close();
} else {
dialog.close();
}
},
child: const Text("确认"),
)
],
),
);
});
return DialogController(dialog);
}
factory DialogController.loadingAlert({
String? title,
String? subTitle,
}) {
final dialog = BaseDialog(
false,
AlignmentDirectional.center,
);
dialog.initContentView((context, isClose, int milliseconds) {
return AnimatedOpacity(
opacity: isClose ? 0.0 : 1,
duration: Duration(milliseconds: milliseconds),
child: AlertDialog(
title: Text(title ?? "正在加载"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 3,),
), // 添加一个加载指示器
const SizedBox(height: 16),
Text(subTitle ?? '请等待...'), // 提示用户等待
],
),
),
);
});
return DialogController(dialog);
}
}