在开发复杂的 Flutter 应用时,弹窗的管理往往是一个令人头疼的问题。尤其是在多个弹窗需要按顺序显示,或者弹窗的显示需要满足特定条件时,手动管理弹窗的显示和隐藏不仅繁琐,还容易出错。为了解决这个问题,我们可以实现一个通用的弹窗队列管理系统,它能够自动管理弹窗的显示顺序,并且支持条件判断,决定是否显示某个弹窗。
一、需求分析
在实现弹窗队列管理系统之前,我们先明确一下需求:
- 支持弹窗队列:能够将多个弹窗按顺序排队,依次显示。
- 条件判断:每个弹窗可以指定一个条件函数,只有当条件满足时,弹窗才会显示。
- 线程安全:在多线程环境下,确保弹窗队列的操作是安全的。
- 通用性:不限制在
StatefulWidget
中使用,可以在任何地方调用。
二、实现思路
为了实现上述需求,我们采用以下设计思路:
- 单例模式:使用单例模式管理弹窗队列,确保全局只有一个队列管理实例。
- 线程安全:使用
synchronized
包来确保对队列的操作是线程安全的。 - 独立函数:提供一个独立的
showQueueDialog
函数,可以在任何地方调用,而不仅仅是StatefulWidget
的State
中。
三、代码实现
以下是完整的代码实现:
1. 弹窗队列管理类
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:synchronized/synchronized.dart';
const _defaultTag = 'default_dialog_queue_tag';
typedef BSQueueDialogCondition = FutureOr<bool> Function(BuildContext context);
typedef BSQueueDialogShow = FutureOr<void> Function(BuildContext context);
class BSQueueDialog {
final BSQueueDialogCondition? shouldShow;
final BSQueueDialogShow show;
const BSQueueDialog({this.shouldShow, required this.show});
}
class DialogQueueManager {
static final DialogQueueManager _instance = DialogQueueManager._internal();
factory DialogQueueManager() => _instance;
DialogQueueManager._internal();
final _dialogQueue = <String, List<BSQueueDialog>>{};
final _displayingDialog = <String, BSQueueDialog>{};
final _lock = Lock();
Future<void> showQueueDialog<R>({
required BuildContext context,
BSQueueDialogCondition? shouldShow,
required BSQueueDialogShow show,
String tag = _defaultTag,
}) async {
final dialog = BSQueueDialog(shouldShow: shouldShow, show: show);
await _lock.synchronized(() async {
var queue = _dialogQueue[tag];
if (queue == null) {
queue = <BSQueueDialog>[];
_dialogQueue[tag] = queue;
}
queue.add(dialog);
final displayingDialog = _displayingDialog[tag];
if (displayingDialog == null) {
_displayingDialog[tag] = dialog;
await _showQueueDialog(tag, context);
}
});
}
Future<void> _showQueueDialog(String tag, BuildContext context) async {
while (true) {
await _lock.synchronized(() async {
final queue = _dialogQueue[tag];
if (queue == null || queue.isEmpty) {
_dialogQueue.remove(tag);
_displayingDialog.remove(tag);
return;
}
final dialog = queue.removeAt(0);
if (!mounted) return;
final shouldShow = await dialog.shouldShow?.call(context) ?? false;
if (!mounted) return;
if (mounted && shouldShow) {
_displayingDialog[tag] = dialog;
} else {
return; // 如果不应该显示,则直接返回
}
});
if (!mounted) return;
await dialog.show(context);
await _lock.synchronized(() {
_displayingDialog.remove(tag);
});
}
}
}
2. 独立的 showQueueDialog
函数
// 独立的 showQueueDialog 函数
Future<void> showQueueDialog<R>({
required BuildContext context,
BSQueueDialogCondition? shouldShow,
required BSQueueDialogShow show,
String tag = _defaultTag,
}) async {
return DialogQueueManager().showQueueDialog(
context: context,
shouldShow: shouldShow,
show: show,
tag: tag,
);
}
3. 使用示例
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Queue Dialog Example',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Queue Dialog Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
showQueueDialog(
context: context,
shouldShow: (context) async {
// 可以在这里添加条件逻辑
return true;
},
show: (context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Queue Dialog'),
content: Text('This is a queued dialog.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Close'),
),
],
),
);
},
);
},
child: Text('Show Queue Dialog'),
),
),
);
}
}
四、代码说明
1. 单例模式
使用 DialogQueueManager
类封装队列管理逻辑,确保全局只有一个实例。通过 factory
构造函数和 _internal
私有构造函数实现单例模式。
2. 线程安全
使用 synchronized
包中的 _lock
对象,确保对 _dialogQueue
和 _displayingDialog
的操作是线程安全的。
3. mounted
检查
在每次使用 context
前,都进行 mounted
检查,确保在 widget 被销毁后不会继续操作 context
。
4. 循环代替递归
使用 while (true)
循环代替递归调用,避免栈溢出问题。
5. 独立的 showQueueDialog
函数
提供了一个独立的 showQueueDialog
函数,可以在任何地方调用,而不仅仅是 StatefulWidget
的 State
中。
五、总结
通过上述实现,我们构建了一个通用的、线程安全的弹窗队列管理系统。这个系统不仅支持弹窗的按序显示,还支持条件判断,决定是否显示某个弹窗。通过提供独立的 showQueueDialog
函数,我们确保了这个系统可以在任何地方使用,而不仅仅是 StatefulWidget
的 State
中。这种方式更加灵活,适用于更多的场景,能够有效简化弹窗的管理逻辑,提高代码的可维护性。