Flutter开发通用页面Loading组件示例详解

作者:Fitem 时间:2022-05-18 23:41:09 

前沿

页面通用Loading组件是一个App必不可少的基础功能,之前只开发过Android原生的页面Loading,这次就按原生的逻辑再开发一个Flutter的Widget,对其进行封装复用

我们先看下效果:

Flutter开发通用页面Loading组件示例详解

原理

状态

一个通用的页面加载Loading组件应该具备以下几种状态:

IDLE 初始化

Idle状态,此时的组件还只是初始化

LOADING 加载中

Loading状态,一般在网络请求或者耗时加载数据时调用,通用显示的是一个progress或者自定义的帧动画

LOADING_SUCCESS

LoadingSuccess加载成功,一般在网络请求成功后调用,并将需要展示的页面展示出来

LOADING_SUCCESS_BUT_EMPTY

页面加载成功但是没有数据,这种情况一般是发起列表数据请求但是没有数据,通常我们会展示一个空数据的页面来提醒用户

NETWORK_BLOCKED

网络错误,一般是由于网络异常、网络请求连接超时导致。此时我们需要展示一个网络错误的页面,并且带有重试按钮,让用户重新发起请求

ERROR

通常是接口错误,这种情况下我们会根据接口返回的错误码或者错误文本提示用户,并且也有重试按钮

/// 状态枚举
enum LoadingStatus {
 idle, // 初始化
 loading, // 加载中
 loading_suc, // 加载成功
 loading_suc_but_empty, // 加载成功但是数据为空
 network_blocked, // 网络加载错误
 error, // 加载错误
}

点击事件回调

当网络异常或者接口报错时,会显示错误页面,并且提供重试按钮,让用户点击重新请求。基于这个需求,我们还需要提供点击重试后的事件回调让业务可以处理重新请求。

/// 定义点击事件
typedef OnTapCallback = Function(LoadingView widget);

提示文案

提供提示文案的自定义,方便业务根据自己的需求展示特定的提示文案

代码实现

根据上面的原理来实现对应的代码

  • 构造方法

/// 构造方法
LoadingView({
   Key key,
   @required this.child, // 需要加载的Widget
   @required this.todoAfterError, // 错误点击重试
   @required this.todoAfterNetworkBlocked, // 网络错误点击重试
   this.networkBlockedDesc = "网络连接超时,请检查你的网络环境",
   this.errorDesc = "加载失败",
   this.loadingStatus = LoadingStatus.idle,
}) : super(key: key);
  • 根据不同的Loading状态展示对应的Widget

  • 其中idle、success状态直接展示需要加载的Widget(这里也可以使用渐变动画进行切换过度)

///根据不同状态展示不同Widget
 Widget _buildBody() {
   switch (widget.loadingStatus) {
     case LoadingStatus.idle:
       return widget.child;
     case LoadingStatus.loading:
       return _buildLoadingView();
     case LoadingStatus.loading_suc:
       return widget.child;
     case LoadingStatus.loading_suc_but_empty:
       return _buildLoadingSucButEmptyView();
     case LoadingStatus.error:
       return _buildErrorView();
     case LoadingStatus.network_blocked:
       return _buildNetworkBlockedView();
   }
   return widget.child;
 }
  • buildLoadingView,这里简单用了系统的CircularProgressIndicator,也可以自己显示帧动画

/// 加载中 View
 Widget _buildLoadingView() {
   return Container(
     width: double.maxFinite,
     height: double.maxFinite,
     child: Center(
       child: SizedBox(
         height: 22.w,
         width: 22.w,
         child: CircularProgressIndicator(
           strokeWidth: 2,
           valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryBgBlue),
         ),
       ),
     ),
   );
 }
  • 其他提示页面,这里做了一个统一的封装

/// 编译通用页面
 Container _buildGeneralTapView({
   String url = "images/icon_network_blocked.png",
   String desc,
   @required Function onTap,
 }) {
   return Container(
     color: AppColors.primaryBgWhite,
     width: double.maxFinite,
     height: double.maxFinite,
     child: Center(
       child: SizedBox(
         height: 250.h,
         child: Column(
           children: [
             Image.asset(url,
                 width: 140.w, height: 99.h),
             SizedBox(
               height: 40.h,
             ),
             Text(
               desc,
               style: AppText.gray50Text12,
               maxLines: 2,
               overflow: TextOverflow.ellipsis,
             ),
             SizedBox(
               height: 30.h,
             ),
             if (onTap != null)
               BorderRedBtnWidget(
                 content: "重新加载",
                 onClick: onTap,
                 padding: 40.w,
               ),
           ],
         ),
       ),
     ),
   );
 }
   /// 加载成功但数据为空 View
 Widget _buildLoadingSucButEmptyView() {
   return _buildGeneralTapView(
     url: "images/icon_empty.png",
     desc: "暂无数据",
     onTap: null,
   );
 }
 /// 网络加载错误页面
 Widget _buildNetworkBlockedView() {
   return _buildGeneralTapView(
       url: "images/icon_network_blocked.png",
       desc: widget.networkBlockedDesc,
       onTap: () {
         widget.todoAfterNetworkBlocked(widget);
       });
 }
 /// 加载错误页面
 Widget _buildErrorView() {
   return _buildGeneralTapView(
       url: "images/icon_error.png",
       desc: widget.errorDesc,
       onTap: () {
         widget.todoAfterError(widget);
       });
 }

使用

Widget _buildBody() {
    var loadingView = LoadingView(
     loadingStatus: LoadingStatus.loading,
     child: _buildContent(),
     todoAfterNetworkBlocked: (LoadingView widget) {
       // 网络错误,点击重试
       widget.updateStatus(LoadingStatus.loading);
       Future.delayed(Duration(milliseconds: 1000), () {
         widget.updateStatus(LoadingStatus.error);
       });
     },
     todoAfterError: (LoadingView widget) {
       // 接口错误,点击重试
       widget.updateStatus(LoadingStatus.loading);
       Future.delayed(Duration(milliseconds: 1000), () {
         // widget.updateStatus(LoadingStatus.loading_suc);
         widget.updateStatus(LoadingStatus.loading_suc_but_empty);
       });
     },
   );
    Future.delayed(Duration(milliseconds: 1000), (){
      loadingView.updateStatus(LoadingStatus.network_blocked);
    });
    return loadingView;
 }

来源:https://juejin.cn/post/7063025458761170981

标签:Flutter,通用页面,Loading,组件
0
投稿

猜你喜欢

  • Android内容提供者ContentProvider用法实例分析

    2021-06-25 09:33:04
  • 在IDEA中配置tomcat并创建tomcat项目的图文教程

    2023-08-11 11:33:59
  • C#实现自定义双击事件

    2023-05-01 16:31:57
  • 详解Java执行groovy脚本的两种方式

    2021-05-28 09:23:27
  • Java Calendar类使用案例详解

    2023-07-09 14:03:22
  • springboot中的pom文件 project报错问题

    2022-01-24 00:41:55
  • Android利用LitePal操作数据库存取图片

    2021-06-14 14:57:45
  • SpringBoot集成阿里云OSS图片上传

    2021-08-15 21:01:55
  • Java数据结构之链表、栈、队列、树的实现方法示例

    2021-10-07 10:40:29
  • java 实现发短信功能---腾讯云短信

    2023-11-29 11:03:49
  • c#集合快速排序类实现代码分享

    2023-03-30 13:38:51
  • Flutter实现自定义筛选框的示例代码

    2022-08-13 21:52:40
  • Android实现多点触控功能

    2021-10-18 08:39:33
  • 使用Filter过滤器中访问getSession()要转化

    2022-10-01 16:20:04
  • RocketMQ消息生产者是如何选择Broker示例详解

    2023-11-10 21:45:49
  • springboot多环境配置文件及自定义配置文件路径详解

    2021-09-30 03:55:54
  • C#实现根据银行卡卡号判断银行名

    2021-08-21 07:14:00
  • Java创建线程的五种写法总结

    2023-01-10 04:39:34
  • 解决IDEA右键没有创建新的package选项的情况

    2022-08-12 03:45:45
  • 利用Android封装一个有趣的Loading组件

    2023-01-07 16:25:28
  • asp之家 软件编程 m.aspxhome.com