实例讲述开发中的图片用户体验要点

语言: CN / TW / HK

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


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

前言

在移动应用中,图片是最佳的视觉冲击元素,色彩丰富、吸引眼球的图片能够刺激用户的点击冲动,进而提高转化率。下面的一张图就是来自淘宝一个典型的对比,右图和左图相比,显然更加具备吸引力。那么在日常开发中有哪些影响图片体验的因素呢? image.png

加载过程

图文列表通常首先吸引眼球的是图片,但是如果体验做得不好差别还挺大的,比如下面这种列表,在加载过程中,由于网络速度较慢,导致图片区域全是空白 —— 这会导致用户无法预测界面的元素,从而影响用户体验。

image.png

在加载过程中,好的用户体验应该是给可预期的结果,而不是盲目猜测。

对于解决这种问题来说,有两种做法,一个是常见的占位图形式,还有一种是骨架屏。对于 Flutter 来说,占位图的形式可以使用 CachedNetworkImage 插件实现。

image.png

示例代码如下,这里使用了 CachedNetworkImageplaceholder 属性来返回一个占位组件,从而指示用户此处是一张图片。加载过程中,相比之前的左侧空了一小半的体验来说好很多。 ```dart class TextImageListItem extends StatelessWidget { final String imageUrl; final String text; const TextImageListItem({ Key? key, required this.imageUrl, required this.text, }) : super(key: key);

@override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(15.0), child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ CachedNetworkImage( imageUrl: imageUrl, placeholder: (context, string) { return Container( width: 150, height: 100.0, color: Colors.grey[300], child: Icon( Icons.image, size: 20.0, color: Colors.grey[400], ), ); }, width: 150, height: 100.0, ), const SizedBox( width: 10.0, ), Expanded( child: Text( text, maxLines: 3, style: const TextStyle( overflow: TextOverflow.ellipsis, ), ), ), ], ), ); } } ```

至于骨架屏,推荐大家可以使用一个叫做 skeletons 的插件,既可以简单地使用也可以自定义骨架屏的样式,下面是使用骨架屏的最简单的方法,如果要和界面保持一致的话需要自定义列表视图。

dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('图文列表'), ), body: Skeleton( isLoading: _isLoading, skeleton: SkeletonListView(), child: ListView.builder( itemBuilder: (context, index) { return const TextImageListItem( imageUrl: 'https://th.bing.com/th/id/OIP.dh9AWBD2vmtsJF6MLwxFdwE9DE?w=277&h=180&c=7&r=0&o=5&pid=1.7', text: '没有 UE 给交互效果怎么办?照着原型开发行不行?一张草图打天下?开发全凭项目经理一句话?这些情况下,如何保障用户体验?本专栏将通过实例来讲如何面向用户体验开发。', ); }, itemCount: 20, ), ), ); } skeletons.gif

错误处理

图片一般情况下是能够正常加载的,但是如果遇到网络超时或者图片文件被误删,就可能加载不出来。这个时候如果没有任何指示,那么体验是很糟糕的。合理的做法是给出一个加载错误的占位图,这个在 CachedNetworkImage 中也提供了相应的属性,可以利用 errorWidget 参数返回加载图片出错时的一个组件替代图片位置。这里我们特意修改了图片链接为一个错误链接来模拟加载错误的情况,然后在图片位置告知用户图片加载失败,代码如下。 dart CachedNetworkImage( imageUrl: imageUrl, placeholder: (context, string) { return Container( width: 150, height: 100.0, color: Colors.grey[300], child: Icon( Icons.image, size: 20.0, color: Colors.grey[400], ), ); }, errorWidget: (context, string, obj) { return Container( width: 150, height: 100.0, color: Colors.grey[300], child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.image_not_supported_outlined, size: 20.0, color: Colors.grey[400], ), Text( '图片加载失败', style: TextStyle( color: Colors.grey[400], fontSize: 12.0, ), ) ], )); }, width: 150, height: 100.0, ),

image.png

这种方式对于图片找不到处理是没问题的,但是如果是网络问题导致的,那么提供一个重新加载按钮体验会更好 —— 毕竟你不能让用户为了重新加载一张图片来个顶部下拉刷新。这种情况如果滑动了好几页的话,那用户还得重新找到之前的位置,体验就不怎么好了。因此,更好的方式是提供一个重新加载按钮,毕竟实际图片文件被误删的可能性很低,网络原因导致加载不出来频次更高(尤其是支持 gif 图片的情况)。这种情况建议对列表元素进行进行二次封装,以便支持单个列表元素重新加载。最终实现的效果如下图所示,点击重新加载会将刷新列表元素组件,从而实现重新加载。

重新加载.gif

控制图片文件大小

这个其实和开发有很大关系,一般来说,如果列表存在图片的,建议生成缩略图。这是因为,如果直接使用原图的话,可能一次性加载几十张图片。现在的手机拍照图片动辄10几 M,如果一下子加载几十张10M 的图片文件,列表大概率会卡死。因此,这种情况推荐是后端同学生成列表所需的缩略图,至于如何裁剪或压缩也一并交给后端吧。

一个非常牛的缩略图编解码库

网上见过一个非常牛的库,叫做 BlurHash,可以将图片编码转换为字符(20-30个字符),然后前端将这字符解码渲染为图片,神奇的地方在于这张图片就像是原图模糊后的图片一样。这种图片可以替代占位图,下面是他们官网的一个演示效果,可以看到,相比灰不溜秋的占位图好很多。 blurhash.gif

总结

图片是前端应用中吸引眼球的主要元素,可以采取如下三种方式提升图片元素的用户体验:

  1. 加载过程中增加占位图或者使用骨架屏;也可以考虑使用 BlurHash 这个库,只是需要前后端配合。
  2. 充分考虑图片加载不出来的用户体验,最好是提供重新加载操作。
  3. 控制列表的图片文件大小,可以要求后端在存储的时候生成尺寸小、经过压缩的缩略图,避免列表开始。

源码地址:开发中的图片用户体验要点