赞
踩
- import 'package:flutter/material.dart';
-
- enum LoadingStyle {
- onlyIndicator, // 仅一个转圈等待
- roundedRectangle, // 添加一个圆角矩形当背景
- maskingOperation, // 添加一个背景蒙层, 阻止用户操作
- }
-
- class LoadingView {
- static final LoadingView _singleton = LoadingView._internal();
-
- factory LoadingView() {
- return _singleton;
- }
-
- LoadingView._internal();
-
- OverlayEntry? _overlayEntry;
-
- void show(BuildContext context, {LoadingStyle type = LoadingStyle.onlyIndicator}) {
- if (_overlayEntry == null) {
- _overlayEntry = _createOverlayEntry(type);
- Overlay.of(context).insert(_overlayEntry!);
- }
- }
-
- void hide() {
- _overlayEntry?.remove();
- _overlayEntry = null;
- }
-
- OverlayEntry _createOverlayEntry(LoadingStyle type) => OverlayEntry(
- builder: (BuildContext context) {
- List<Widget> stackChildren = [];
- if (type == LoadingStyle.roundedRectangle) {
- stackChildren.add(
- Center(
- child: Container(
- width: 100,
- height: 100,
- padding: const EdgeInsets.all(20.0),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(8.0),
- ),
- child: const Align(
- alignment: Alignment.center,
- child: SizedBox(
- width: 45,
- height: 45,
- child: CircularProgressIndicator(
- color: Colors.black,
- ),
- ),
- ),
- ),
- ),
- );
- } else if (type == LoadingStyle.maskingOperation) {
- stackChildren.addAll([
- const Opacity(
- opacity: 0.5,
- child: ModalBarrier(dismissible: false, color: Colors.black),
- ),
- const Center(child: CircularProgressIndicator()),
- ]);
- } else {
- stackChildren.add(
- const Center(child: CircularProgressIndicator()),
- );
- }
-
- return Stack(children: stackChildren);
- },
- );
- }

- import 'package:flutter/material.dart';
-
- enum ToastPosition { center, bottom }
-
- class ToastView {
- static OverlayEntry? _overlayEntry;
-
- static void showToast(
- BuildContext context,
- String message, {
- ToastPosition position = ToastPosition.center,
- int second = 2,
- Color backgroundColor = Colors.white,
- Color textColor = Colors.black,
- double horizontalMargin = 16,
- EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
- }) {
- _overlayEntry?.remove();
-
- _overlayEntry = OverlayEntry(
- builder: (context) => ToastWidget(
- message: message,
- position: position,
- backgroundColor: backgroundColor,
- textColor: textColor,
- horizontalMargin: horizontalMargin,
- padding: padding,
- ),
- );
-
- Overlay.of(context)?.insert(_overlayEntry!);
-
- Future.delayed(Duration(seconds: second), () {
- _overlayEntry?.remove();
- _overlayEntry = null;
- });
- }
- }
-
- class ToastWidget extends StatelessWidget {
- final String message;
- final ToastPosition position;
- final Color backgroundColor;
- final Color textColor;
- final double horizontalMargin;
- final EdgeInsetsGeometry padding;
-
- const ToastWidget({
- Key? key,
- required this.message,
- required this.position,
- required this.backgroundColor,
- required this.textColor,
- required this.horizontalMargin,
- required this.padding,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return Positioned(
- top: position == ToastPosition.center ? MediaQuery.of(context).size.height / 2 : null,
- bottom: position == ToastPosition.bottom ? 50.0 : null,
- left: horizontalMargin,
- right: horizontalMargin,
- child: Material(
- color: Colors.transparent,
- child: Align(
- alignment: position == ToastPosition.center ? Alignment.center : Alignment.bottomCenter,
- child: FittedBox(
- fit: BoxFit.scaleDown,
- child: Container(
- padding: padding,
- decoration: BoxDecoration(
- color: backgroundColor,
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(
- message,
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 15,
- color: textColor,
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }
- }

经过上面自定义视图,我们注意到,视图的展示都需要BuildContext context。若是这样的话,就强行将弹窗视图的逻辑绑定到了具体的某个组件上,导致组件销毁时弹窗也必须销毁。否则,context都消失了,你又如何去处理插入其中的视图?
我们往往需要,让等待转圈在离开页面后还继续展示,让Toast在关闭页面时也不被影响到其提示的时长。
所以,这里我们用了一个全局回调管理。
- enum AlertCallbackType { none, showLoading, hideLoading, showToast }
-
- class AlertCallbackManager {
- // 私有构造函数
- AlertCallbackManager._privateConstructor();
-
- // 单例实例
- static final AlertCallbackManager _instance = AlertCallbackManager._privateConstructor();
-
- // 获取单例实例的方法
- static AlertCallbackManager get instance => _instance;
-
- // 定义闭包类型的回调函数
- Function(AlertCallbackType type, String message)? callback;
- }
有了全局回调管理,我们还需要有一个不会被轻易销毁的根组件,来提供BuildContext context。
注意:全局提示回调, 要放在MaterialApp包装之后,因为这里的LoadingView实现方式需要放在MaterialApp之下。
- void main() async {
- WidgetsFlutterBinding.ensureInitialized();
- runApp(const MyApp());
- }
-
- // MARK: 用来包装MaterialApp
- class MyApp extends StatelessWidget {
- const MyApp({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const MaterialApp(
- title: 'Flutter Demo',
- debugShowCheckedModeBanner: false, // 禁用调试标签
- home: BaseWidget(),
- );
- }
- }
-
- // MARK: 根组件 用来处理公用逻辑
- class BaseWidget extends StatefulWidget {
- const BaseWidget({super.key});
-
- @override
- State<BaseWidget> createState() => _BaseWidgetState();
- }
-
- class _BaseWidgetState extends State<BaseWidget> {
- @override
- void initState() {
- super.initState();
- // 提示回调, 要放在MaterialApp包装之后
- AlertCallbackManager.instance.callback = (type, message) async {
- if (mounted) { // 检查当前State是否仍然被挂载(即没有被dispose)
- if (type == AlertCallbackType.showLoading) {
- LoadingView().show(context);
- } else if (type == AlertCallbackType.hideLoading) {
- LoadingView().hide();
- } else if (type == AlertCallbackType.showToast) {
- ToastView.showToast(context, message);
- }
- }
- };
- }
-
- @override
- void dispose() {
- LoadingView().hide();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return const HomePage();
- }
- }

然后在需要展示的地方,用如下方式调用,最好再进一步将方法封装得短一些。
- AlertCallbackManager.instance.callback?.call(AlertCallbackType.showLoading, "");
-
- AlertCallbackManager.instance.callback?.call(AlertCallbackType.hideLoading, "");
-
- AlertCallbackManager.instance.callback?.call(AlertCallbackType.showToast, "message");
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。