美文网首页Flutter
Flutter页面通用Loading组件

Flutter页面通用Loading组件

作者: Fitem | 来源:发表于2021-06-01 17:49 被阅读0次

    前沿

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


    bloggif_60b603bed8875.gif

    原理

    状态

    一个通用的页面加载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);
    

    提示文案

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

    代码实现

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

    1. 构造方法
     /// 构造方法
     LoadingView({
        Key key,
        @required this.child, // 需要加载的Widget
        @required this.todoAfterError, // 错误点击重试
        @required this.todoAfterNetworkBlocked, // 网络错误点击重试
        this.networkBlockedDesc = "网络连接超时,请检查你的网络环境",
        this.errorDesc = "加载失败",
        this.loadingStatus = LoadingStatus.idle,
     }) : super(key: key);
    
    1. 根据不同的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;
      }
    
    1. 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),
              ),
            ),
          ),
        );
      }
    
    1. 其他提示页面,这里做了一个统一的封装
    
     /// 编译通用页面
      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;
      }
    

    总结

    至此已经完成了对整个Loading组件的封装,代码已上传Github

    相关文章

      网友评论

        本文标题:Flutter页面通用Loading组件

        本文链接:https://www.haomeiwen.com/subject/xbqgsltx.html