前言:
我们应用开发中 很多地方都需要和后端进行通信。或者在做一些耗时操作的时候。我们不希望用户在没有返回数据的时候 进行点击。或者二次提交这个时候 需要一个 Loading 的弹框。来给用户展示数据提交或者响应中。
1.照例我们先看效果

就是上面类型的效果。一个可以提示用户 和 阻断用户继续点击的这么一个显示,为了达到理想的效果。
- 其实我们点击按钮的时候 是调用了多次的。这个场景就是 同时调用多次。但是只有一个在展示的的。
- 内部维护一个计时器,超过指定时间没有收到 hideLoading.指令能自己取消。防止某些情况下 漏掉取消
- 出现多次点击的时候我们以最后一次请求重置 计时时间
下面是我们的测试代码调用。类似模拟多次调用:
onTap: () { AppOverlay().showLoading(); AppOverlay().showLoading(); AppOverlay().showLoading(); AppOverlay().hideLoading(); AppOverlay().hideLoading(); AppOverlay().hideLoading(); AppOverlay().showLoading(); AppOverlay().hideLoading(); AppOverlay().showLoading(); AppOverlay().showLoading(); AppOverlay().hideLoading(); AppOverlay().hideLoading(); AppOverlay().showLoading(); Future.delayed(const Duration(seconds: 1)).then((value) { AppOverlay().hideLoading(); }); },
2.实现思路介绍
内部使用:OverlayEntry。 这个东西很好用,可以直接盖住 其他组件。悬浮于其他组件之上。这样我们就能更好地控制 这个 OverlayEntry
3.具体的实现
开箱即用:
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:game/const/app_textStyle.dart'; import 'package:game/utils/app_screen.dart'; import 'package:game/wrap/extension/extension.dart'; import '../../utils/app_colors.dart'; class AppOverlay { //单例 static final AppOverlay _instance = AppOverlay._internal(); /// 页面引用计数器 static int _count = 0; /// 超时时间 final int _durationMaxTime = 10; /// 当前计时器时间 int _durationCurrentTime = 0; final Duration duration = const Duration(seconds: 1); /// 计时器 Timer? _timer; factory AppOverlay() { return _instance; } OverlayEntry? _overlayEntry; AppOverlay._internal(); // 不需要初始化 showLoading({String? title}) { _showLoading(title: title); } hideLoading() { _hideLoading(); } _showLoading({String? title}) async { Future.delayed(Duration.zero, () { _durationCurrentTime = 0; AppOverlay._count++; _timer ??= Timer.periodic(duration, (timer) { //print('_showLoading timer:$_durationCurrentTime'); if (_overlayEntry != null) { _durationCurrentTime++; if (_durationCurrentTime > _durationMaxTime) { _hideLoading(); } } else { timer.cancel(); _timer = null; } }); if (_overlayEntry == null) { _overlayEntry = OverlayEntry(builder: (BuildContext context) { //外层使用Positioned进行定位,控制在Overlay中的位置 return Positioned( child: Material( color: Colors.transparent, child: Container( // width: MediaQuery.of(AppScreen.buildContext).size.width, alignment: Alignment.center, child: Center( child: Container( constraints: BoxConstraints( minHeight: AppScreen.calc(160), minWidth: AppScreen.calc(160), maxWidth: 520.cale, ), decoration: BoxDecoration( //color: AppColor.colordddddd, color: AppColor.disabledColor, borderRadius: BorderRadius.circular(AppScreen.calc(24)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: title != null ? EdgeInsets.only(top: 40.cale, bottom: 30.cale) : EdgeInsets.symmetric(vertical: 40.cale), child: const CircularProgressIndicator( //color: AppColor.primaryColor, color: Colors.white, ), ), if (title != null) Padding( padding: EdgeInsets.only( left: 24.cale, right: 24.cale, bottom: 40.cale, ), child: Text( title, style: AppTextStyle.textStyle_32_FFFFFF, textAlign: TextAlign.center, ), ), ], ), ), ), ), ), ); }); //往Overlay中插入插入OverlayEntry AppScreen.overlay!.insert(_overlayEntry!); } }); } _hideLoading() { Future.delayed(Duration.zero, () { AppOverlay._count--; if (AppOverlay._count <= 0) { AppOverlay._count = 0; if (_overlayEntry != null) { _overlayEntry!.remove(); _overlayEntry = null; } else { print("_overlayEntry is null"); } if (AppOverlay._count <= 0) { _timer?.cancel(); _timer = null; } } else { print("AppOverlay Using"); } }); } }