列表的加载过程很重要的!

语言: CN / TW / HK

theme: v-green highlight: atom-one-dark


本文为掘金社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

如果要说哪些元素是 App 最常见的,列表一定位列其中。资讯、商品、联系人、消息……等等都是以列表的形式呈现在我们的手机屏幕上。可以说,我们每天打交道最多的就是列表,在手指滑动之间,时间真的在指缝中流逝。列表中,最为关键的体验是加载过程的处理,必须做好体验。

进入列表

首次进入列表时,这个时候通常会是一个空白页面,而人天生对未知的事物感到不安。因此,进入列表等待的过程一直保持空白的体验是很糟糕的。我们来看看常见的三种进入列表的页面加载情形。 第一种,最普通,就是在加载过程中给一个转圈指示,这是很多开发者接触最多的实现方法,因为最简单!比如拿 Flutter 来说,三元操作符就搞定了。当然,我还见过最偷懒的,就是连个转圈都没有,直接白屏让用户干巴巴地等,碰到网络不好,用户等到花都谢了 —— 当然,App 也被卸了dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('加载'), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : ListView(), ); } empty_loading.gif 第二种,其实还是loading 动效,只是增强趣味性来提高用户等待的体验,下面是从 Dribble 找到的一个有趣的 loading 动效。说实话,我碰到这种有趣的运动 loading,总是会试图找出其中的运动规律,所以等待一会有时候完全感受不到等待的过程。 loading.gif 这种动效自己写的话还挺费时间的,不过有相应的工具来制作,比如Airbnb推出的 Lottie,适用于 Android、iOS 和 React Native,可以将 AE (Adode Effect)制作的动效直接渲染成动画。对于 Flutter,有人将 Lottie 移植到了 Flutter,也是大受欢迎,包地址为:Flutter版 Lottie。Lottie 使用非常简单,从本地加载 AE 导出的json 文件即可,也支持从压缩包或网络加载动画文件。我们来看一个示例,这是一个运输货车的动画。所以,以后设计师出 loading 动效让你实现的时候,你可以让他放马过来了! dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('加载'), ), body: _isLoading ? Center( child: Lottie.asset( 'assets/delivery_van.json', repeat: true, ), ) : ListView(), ); } Lottie_van.gif 第三种是骨架屏,这种目前在很多信息流类的 App 上已经很常见了。说实话,早期出来的时候感觉还挺经验的,目前已经见怪不怪了。骨架屏在之前的图片体验篇有介绍过了,这里就不再赘述。

下拉刷新

下拉刷新已经非常普遍了,可以说通过下拉和上滑加载更多是手机端列表交互非常大的创新。在此之前,还有模仿 PC 端做分页的处理,那种体验远不如下拉刷新和上滑加载更多。与空白页加载类似,改善体验的关键点在于优化等待过程,比如加载过程中给一个有趣的动画效果。这里推荐 Flutter 一个不错的组件:custom_refresh_indicator。下面是结合了之前的一篇自定义 loading 动效的示例代码: dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('加载'), ), body: CustomRefreshIndicator( onRefresh: refresh, builder: (context, child, controller) { return Stack(alignment: Alignment.topCenter, children: [ AnimatedBuilder( builder: (context, _) { return Transform.translate( offset: Offset(0.0, controller.value * _loadingHeaderSize), child: child, ); }, animation: controller, ), controller.state.isLoading ? LoadingAnimations( foregroundColor: Colors.blue, bgColor: Colors.transparent, size: _loadingHeaderSize, ) : const SizedBox(height: 0.0) ]); }, child: ListView.builder( itemBuilder: (context, index) { return const ListItem(); }, itemCount: 10, ), ), ); } } 实现效果如下所示,通过有趣的下拉刷新动画可以相当大程度缓解用户等待过程中的焦虑。 下拉刷新.gif

上滑加载更多

上滑加载更多和下拉刷新其实是一样的,只是也需要在等待过程给予有趣的提示。这里提出来主要是顺带讲一下 custom_refresh_indicator的上滑加载更多。CustomRefreshIndicator有两个关键的属性:

  • trigger:决定触发刷新的方向,是下拉(leadingEdge)还是上滑(trailingEdge),或者同时触发(bothEdges)。因此,如果要同时支持下拉刷新和上滑加载更多就需要设置为bothEdges
  • controller:整个CustomRefreshIndicator的状态信息都由controller存储,因此可以获取滑动过程中的状态,比如滑动方向,释放后滑动过程的比例等等。我们可以根据这个来控制等待动画显示的位置。这可以让我们通过一个 AnimationBuilder 来构建松开手释放后的动画过程。

比如下面的代码就是在下拉刷新的基础上增加了上滑加载更多的效果。这里一个是通过滑动方向设置 offsetRatio的符号来控制释放后列表是往上移动(下拉刷新)还是往下移动(上滑加载)。另一个就是根据滑动的方向决定 Stack 组件里的加载动画组件是顶部对齐还是底部对齐,从而控制加载动画组件出现是在顶部(下拉刷新)还是底部(上滑加载)。 dart CustomRefreshIndicator( onRefresh: refresh, builder: (context, child, controller) { double offsetRatio = controller.scrollingDirection == ScrollDirection.forward ? 1.0 : -1.0; return Stack( alignment: controller.scrollingDirection == ScrollDirection.forward ? Alignment.topCenter : Alignment.bottomCenter, children: [ AnimatedBuilder( builder: (context, _) { return Transform.translate( offset: Offset(0.0, offsetRatio * controller.value * _loadingHeaderSize), child: child, ); }, animation: controller, ), controller.state.isLoading ? LoadingAnimations( foregroundColor: Colors.blue, bgColor: Colors.transparent, size: _loadingHeaderSize, ) : const SizedBox(height: 0.0) ], ); }, trigger: IndicatorTrigger.bothEdges, child: ListView.builder( itemBuilder: (context, index) { return const ListItem(); }, itemCount: 10, ), ), 运行的效果如下面的动图所示。 下拉刷新和上滑加载.gif

总结

列表作为 App 中最为常用的组件之一,用户使用列表时,会面临进入列表、下拉刷新和上滑加载三个加载等待过程。请记住提高列表等待过程体验的三个原则:

  1. 尽量不要让用户等待太久,比如提高加载速度;
  2. 尽量通过有趣的动画提高等待过程中的趣味性。
  3. 切忌,不要让用户没有任何预期地等。像那种进入后直接白屏,没有任何反馈的等待形式是体验最糟糕的!