Flutter加载(loading)页面 2023-01-20
需求简介
一般的页面,大多数可以分为三种状态:
-
数据加载完成,正常显示;
-
数据加载完成,但是没有数据,一般会有一个空数据视图;
加载后网络错误一般给个toast提示就好,给个错误视图并没有什么价值。 -
加载过程,一般是loading视图,这个就是当前的话题;
简单实现
一般的toast插件,都会提供一个loading视图。比如我们现在用的bot_toast插件,大多数时候用的是toast提示。网络访问时的loading视图用的也是他,而且直接做在底层的request接口。只要有网络访问,就自动显示。
企业微信截图_62fa2228-9bcf-473d-81e1-47bfbb65f5d3.png自定义视图
如果简单的loading不喜欢,可以考虑自定义写一个。
这里有一个非常好用的插件lottie
他的特点是,先用AE工具做一个动画,然后借助工具Bodymovin转化为json文件。把这个json文件以资源的形式加入工程,然后就可以用这个lottie工具进行展示。
企业微信截图_efb3cd09-06a4-4605-a379-710af219e2fc.png这样出来的loading动画比直接使用gif文件,质量上要好很多。所以这个插件还是很受欢迎的。
企业微信截图_41311865-2609-4049-9cee-5ef53829f304.png使用
-
把动画json文件以资源的形式加入工程。
-
类似图片,用lottie工具加载。
封装
-
lottie工具实现的是动画以json资源文件的形式加载,并且画面质量很高。
-
就像上面的视图,4个跳动的绿点是动画,但是灰色框,以及外面看不见的蒙层,那是需要自己写的。
loading视图
Widget get _loadingView {
// return Center(
// child: Lottie.network('https://assets3.lottiefiles.com/private_files/lf30_gvdwzaoj.json'),
// );
// return Center(child: Lottie.asset("assets/loading.json"));
return Center(
child: Container(
color: Colors.transparent,
height: ScreenUtil().screenHeight,
width: ScreenUtil().screenWidth,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: const Color(0x99000000),
borderRadius: BorderRadius.all(Radius.circular(5.w)),
),
width: 100.w,
height: 100.w,
child: Center(
child: SizedBox(
width: 60.w,
height: 60.w,
child: Lottie.asset("assets/lottie/loading.json"),
),
),
),
],
),
),
);
}
-
Lottie加载的动画是60x60的跳动的绿点;
-
绿点包在一个100x100的透明度60%黑色,带圆角的矩形中
-
最外层是一个全屏尺寸的无色矩形,防止点击。
布局实现
这个loading视图做成了一个公共组件,主要的布局代码如下:
@override
Widget build(BuildContext context) {
if (cover) {
return Stack(
children: [child, isLoading ? _loadingView : Container()],
);
} else {
return isLoading ? _loadingView : child;
}
}
-
cover参数默认为true,就是loading视图以浮层的形式覆盖在原视图上(Stack组件实现)。为false的时候,就是只显示loading视图,不显示原视图。
-
做成了公共组件,原视图以child的方式加入,然后使用者自己控制isLoading参数来达到loading视图的显示和隐藏。这个有点“套娃”的味道,和传统的继承感觉不大一样。
使用
-
如果要全屏控制,可以把整个Scaffold都当做child传入;
-
大多数时候,只要把body作为child的就可以了。(loading过程,可以点导航栏的返回按钮)
body: LottieUtils(
isLoading: logic.isShowLoading,
cover: logic.hasData,
child: buildBody(),
),
- 使用两三个逻辑变量来控制loading视图的显示和隐藏
/// 加载过程标记
/// 是否有数据; 根据接口返回的total来判断
/// 无数据,只显示loading,cover参数为false;
/// 有数据,loading展示在原有视图之上,cover参数为true;
bool get hasData {
return total > 0;
}
bool isDataReady = false;
bool get isShowLoading {
return !isDataReady;
}
- 一般可以直接用在网络接口数据的访问中
/// 获取预演包裹列表; 上拉,下拉,接口封装
/// 加载过程标记做在这个接口上
Future getRehearList({bool isRefresh = false}) async {
/// 接口开始,显示loading
isDataReady = false;
update();
StorageApi.previewItem(
parameters: queryParams,
success: (response) {
/// 成功,隐藏loading
isDataReady = true;
update();
/// 其他业务处理代码
},
fail: (e) {
/// 失败,隐藏loading
isDataReady = true;
update();
},
);
}
全局loading
在大多数情况下,上面的loading方案已经够用。但是,某些特殊情况,就力不从心。
上面的Loading和具体的页面深度绑定,有具体的上下文环境,可以工作很好。
对于那些离开具体上下文全局场景,需要那种浮层式的全局loading。
借用插件
全局浮层写起来麻烦,那么可以考虑借用插件来实现。工程中的toast用的是bot_toast,那么就可以借用这个来实现。
- 既然是全局的loading,那么就直接做成静态函数。
static showToastLoading() {
BotToast.showCustomLoading(
toastBuilder: (cancelFunc) {
return _loadingView;
},
);
}
static hideToastLoading() {
BotToast.closeAllLoading();
}
- 具体的 _loadingView,就是上面用到的
static Widget get _loadingView {
return Center(
child: Container(
color: Colors.transparent,
height: ScreenUtil().screenHeight,
width: ScreenUtil().screenWidth,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: const Color(0x99000000),
borderRadius: BorderRadius.all(Radius.circular(5.w)),
),
width: 100.w,
height: 100.w,
child: Center(
child: SizedBox(
width: 60.w,
height: 60.w,
child: Lottie.asset("assets/lottie/loading.json"),
),
),
),
],
),
),
);
}
- 使用起来和BotToast一样简单,只要保证LottieUtils.showToastLoading()和LottieUtils.hideToastLoading()成对出现就可以了,不需要考虑具体的布局。反正一律是全屏的浮层。