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

前沿

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

我们先看下效果:

原理

状态

一个通用的页面加载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;
  }
复制代码

总结

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

以上就是Flutter开发通用页面Loading组件示例详解的详细内容,更多关于Flutter通用页面Loading组件的资料请关注脚本之家其它相关文章!

你可能感兴趣的:(Flutter开发通用页面Loading组件示例详解)