Flutter Loading 的封装

前言:

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

1.照例我们先看效果

就是上面类型的效果。一个可以提示用户 和 阻断用户继续点击的这么一个显示,为了达到理想的效果。

  1. 其实我们点击按钮的时候 是调用了多次的。这个场景就是 同时调用多次。但是只有一个在展示的的。
  2. 内部维护一个计时器,超过指定时间没有收到 hideLoading.指令能自己取消。防止某些情况下 漏掉取消
  3. 出现多次点击的时候我们以最后一次请求重置 计时时间

下面是我们的测试代码调用。类似模拟多次调用:

复制代码
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");
      }
    });
  }
}
相关推荐
未来猫咪花几秒前
🔥 神奇的 Dart Zone 机制
flutter
AskHarries1 小时前
RevenueCat 接入 Apple App Store 订阅全流程详解(2025 最新)
flutter·ios·app
白茶三许2 小时前
关于Flutter版本过低导致鸿蒙虚拟机启动失败的问题解决
flutter·开源·harmonyos·openharmony
消失的旧时光-194315 小时前
Flutter 与 React/Vue 为什么思想一致?——声明式 UI 体系的深度对比(超清晰版)
vue.js·flutter·react.js
rainboy19 小时前
Flutter :自己动手,封装一个小巧精致的气泡弹窗库
前端·flutter·github
旧时光_21 小时前
第4章:布局类组件 —— 4.5 流式布局(Wrap、Flow)
flutter
程序员老刘21 小时前
Flutter 3.38 版本更新:客户端开发者需要关注这三点?
flutter·客户端
AskHarries1 天前
RevenueCat 接入 Google Play 订阅全流程详解(2025 最新)
android·flutter·google
不凡的凡1 天前
flutter 管理工具fvm
flutter·harmonyos
消失的旧时光-19431 天前
我如何理解 Flutter 本质
android·前端·flutter