13 | 经典控件(二):UITableView/ListView在Flutter中是什么?
下载APP
关闭
渠道合作
推荐作者
13 | 经典控件(二):UITableView/ListView在Flutter中是什么?
2019-07-27 陈航 来自北京
《Flutter核心技术与实战》
课程介绍
讲述:陈航
时长13:25大小12.28M
你好,我是陈航。
在上一篇文章中,我和你一起学习了文本、图片和按钮这 3 大经典组件在 Flutter 中的使用方法,以及如何在实际开发中根据不同的场景,去自定义展示样式。
文本、图片和按钮这些基本元素,需要进行排列组合,才能构成我们看到的 UI 视图。那么,当这些基本元素的排列布局超过屏幕显示尺寸(即超过一屏)时,我们就需要引入列表控件来展示视图的完整内容,并根据元素的多少进行自适应滚动展示。
这样的需求,在 Android 中是由 ListView 或 RecyclerView 实现的,在 iOS 中是用 UITableView 实现的;而在 Flutter 中,实现这种需求的则是列表控件 ListView。
ListView
在 Flutter 中,ListView 可以沿一个方向(垂直或水平方向)来排列其所有子 Widget,因此常被用于需要展示一组连续视图元素的场景,比如通信录、优惠券、商家列表等。
我们先来看看 ListView 怎么用。ListView 提供了一个默认构造函数 ListView,我们可以通过设置它的 children 参数,很方便地将所有的子 Widget 包含到 ListView 中。
不过,这种创建方式要求提前将所有子 Widget 一次性创建好,而不是等到它们真正在屏幕上需要显示时才创建,所以有一个很明显的缺点,就是性能不好。因此,这种方式仅适用于列表中含有少量元素的场景。
如下所示,我定义了一组列表项组件,并将它们放在了垂直滚动的 ListView 中:
备注:ListTile 是 Flutter 提供的用于快速构建列表项元素的一个小组件单元,用于 1~3 行(leading、title、subtitle)展示文本、图标等视图元素的场景,通常与 ListView 配合使用。
运行效果,如下图所示:
图 1 ListView 默认构造函数
除了默认的垂直方向布局外,ListView 还可以通过设置 scrollDirection 参数支持水平方向布局。如下所示,我定义了一组不同颜色背景的组件,将它们的宽度设置为 140,并包在了水平布局的 ListView 中,让它们可以横向滚动:
运行效果,如下图所示:
图 2 水平滚动的 ListView
在这个例子中,我们一次性创建了 6 个子 Widget。但从图 2 的运行效果可以看到,由于屏幕的宽高有限,同一时间用户只能看到 3 个 Widget。也就是说,是否一次性提前构建出所有要展示的子 Widget,与用户而言并没有什么视觉上的差异。
所以,考虑到创建子 Widget 产生的性能问题,更好的方法是抽象出创建子 Widget 的方法,交由 ListView 统一管理,在真正需要展示该子 Widget 时再去创建。
ListView 的另一个构造函数 ListView.builder,则适用于子 Widget 比较多的场景。这个构造函数有两个关键参数:
itemBuilder,是列表项的创建方法。当列表滚动到相应位置时,ListView 会调用该方法创建对应的子 Widget。
itemCount,表示列表项的数量,如果为空,则表示 ListView 为无限列表。
同样地,我通过一个案例,与你说明 itemBuilder 与 itemCount 这两个参数的具体用法。
我定义了一个拥有 100 个列表元素的 ListView,在列表项的创建方法中,分别将 index 的值设置为 ListTile 的标题与子标题。比如,第一行列表项会展示 title 0 body 0:
这里需要注意的是,itemExtent 并不是一个必填参数。但,对于定高的列表项元素,我强烈建议你提前设置好这个参数的值。
因为如果这个参数为 null,ListView 会动态地根据子 Widget 创建完成的结果,决定自身的视图高度,以及子 Widget 在 ListView 中的相对位置。在滚动发生变化而列表项又很多时,这样的计算就会非常频繁。
但如果提前设置好 itemExtent,ListView 则可以提前计算好每一个列表项元素的相对位置,以及自身的视图高度,省去了无谓的计算。
因此,在 ListView 中,指定 itemExtent 比让子 Widget 自己决定自身高度会更高效。
运行这个示例,效果如下所示:
图 3 ListView.builder 构造函数
可能你已经发现了,我们的列表还缺少分割线。在 ListView 中,有两种方式支持分割线:
一种是,在 itemBuilder 中,根据 index 的值动态创建分割线,也就是将分割线视为列表项的一部分;
另一种是,使用 ListView 的另一个构造方法 ListView.separated,单独设置分割线的样式。
第一种方式实际上是视图的组合,之前的分享中我们已经多次提及,对你来说应该已经比较熟悉了,这里我就不再过多地介绍了。接下来,我和你演示一下如何使用 ListView.separated 设置分割线。
与 ListView.builder 抽离出了子 Widget 的构建方法类似,ListView.separated 抽离出了分割线的创建方法 separatorBuilder,以便根据 index 设置不同样式的分割线。
如下所示,我针对 index 为偶数的场景,创建了绿色的分割线,而针对 index 为奇数的场景,创建了红色的分割线:
运行效果,如下所示:
图 4 ListView.separated 构造函数
好了,我已经与你分享完了 ListView 的常见构造函数。接下来,我准备了一张表格,总结了 ListView 常见的构造方法及其适用场景,供你参考,以便理解与记忆:
图 5 ListView 常见的构造方法及其适用场景
CustomScrollView
好了,ListView 实现了单一视图下可滚动 Widget 的交互模型,同时也包含了 UI 显示相关的控制逻辑和布局模型。但是,对于某些特殊交互场景,比如多个效果联动、嵌套滚动、精细滑动、视图跟随手势操作等,还需要嵌套多个 ListView 来实现。这时,各自视图的滚动和布局模型就是相互独立、分离的,就很难保证整个页面统一一致的滑动效果。
那么,Flutter 是如何解决多 ListView 嵌套时,页面滑动效果不一致的问题的呢?
在 Flutter 中有一个专门的控件 CustomScrollView,用来处理多个需要自定义滚动效果的 Widget。在 CustomScrollView 中,这些彼此独立的、可滚动的 Widget 被统称为 Sliver。
比如,ListView 的 Sliver 实现为 SliverList,AppBar 的 Sliver 实现为 SliverAppBar。这些 Sliver 不再维护各自的滚动状态,而是交由 CustomScrollView 统一管理,最终实现滑动效果的一致性。
接下来,我通过一个滚动视差的例子,与你演示 CustomScrollView 的使用方法。
视差滚动是指让多层背景以不同的速度移动,在形成立体滚动效果的同时,还能保证良好的视觉体验。 作为移动应用交互设计的热点趋势,越来越多的移动应用使用了这项技术。
以一个有着封面头图的列表为例,我们希望封面头图和列表这两层视图的滚动联动起来,当用户滚动列表时,头图会根据用户的滚动手势,进行缩小和展开。
经分析得出,要实现这样的需求,我们需要两个 Sliver:作为头图的 SliverAppBar,与作为列表的 SliverList。具体的实现思路是:
在创建 SliverAppBar 时,把 flexibleSpace 参数设置为悬浮头图背景。flexibleSpace 可以让背景图显示在 AppBar 下方,高度和 SliverAppBar 一样;
而在创建 SliverList 时,通过 SliverChildBuilderDelegate 参数实现列表项元素的创建;
最后,将它们一并交由 CustomScrollView 的 slivers 参数统一管理。
具体的示例代码如下所示:
运行一下,视差滚动效果如下所示: