Flutter开发通用页面Loading组件示例详解
Fitem 人气:0前沿
页面通用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
加载全部内容