07.手势交互类组件
2024/10/3大约 6 分钟
07.手势交互类组件
7.1 可滚动组件
7.1.1 按需加载布局
- 通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵
- 为此,
Flutter中提出一个Sliver(中文为“薄片”的意思)概念,Sliver可以包含一个或多个子组件- 主要作用是配合:加载子组件并确定每一个子组件的布局和绘制信息,如果
Sliver可以包含多个子组件时,通常会实现按需加载模型
- 主要作用是配合:加载子组件并确定每一个子组件的布局和绘制信息,如果
可滚动组件
- Flutter 中的可滚动组件主要由三个角色组成:
Scrollable、Viewport和Sliver:Scrollable:用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建ViewportViewport:显示的视窗,即列表的可视区域Sliver:视窗里显示的元素
- 具体布局过程:
Scrollable监听到用户滑动行为后,根据最新的滑动偏移构建ViewportViewport将当前视口信息和配置信息通过SliverConstraints传递给SliverSliver中对子组件(RenderBox)按需进行构建和布局,然后确认自身的位置、绘制等信息,保存在geometry中(一个SliverGeometry类型的对象)
- 相关布局结构如下图,其中顶部和底部灰色的区域为
cacheExtent,它表示预渲染的高度,,如果RenderBox进入这个区域内,即使它还未显示在屏幕上,也是要先进行构建的,cacheExtent的默认值是250(可设置,最终会传给Viewport)
- 几乎所有的可滚动组件在构造时都能指定
scrollDirection(滑动的主轴)、reverse(滑动方向是否反向)、controller(滚动控制对象)、physics(动画效果) 、cacheExtent(预加载的区域高度),这些属性最终也会透传给对应的Scrollable和Viewport
7.1.2 SingleChildScrollView
SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子组件,实现最基本的滚动内容功能- 通常
SingleChildScrollView只应在期望的内容不会超过屏幕太多时使用,这是因为SingleChildScrollView不支持基于Sliver的延迟加载模型,所以如果预计视口可能包含超出屏幕尺寸太多的内容时,那么使用SingleChildScrollView将会非常昂贵 - 通常和
Scrollbar(提供滚动条)一起使用,作为它的子组件,此时,记得设置primary为true
SingleChildScrollView({
this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
this.reverse = false,
this.padding,
// 是否使用 widget 树中默认的PrimaryScrollController
// MaterialApp 组件树中已经默认包含一个 PrimaryScrollController
bool primary,
this.physics,
this.controller,
this.child,
})7.1.3 ListView
ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建)
ListView({
...
//可滚动widget公共参数
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController? controller,
bool? primary,
ScrollPhysics? physics,
EdgeInsetsGeometry? padding,
// --- ListView各个构造函数的共同参数 ---
// 如果每个列表项高度相同可以指定比在子组件中设置有更好性能
// 因为不指定,每次渲染都要计算一边,此值与prototypeItem互斥
double? itemExtent, // 列表项沿主轴方向的长
// 如果每个列表项高度相同可以指定获得更好性能,建议优先使用此方法
// 指定后会自动计算列表项沿主轴方向的长度,此值与itemExtent互斥
Widget? prototypeItem, // 列表项原型,即填充了默认值的列表子项
// 默认情况下,ListView会在滚动方向尽可能多的占用空间
// 当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true
bool shrinkWrap = false, // 是否根据子组件的总长度来设置ListView的长度
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double? cacheExtent, // 预渲染区域长度
//子widget列表
List<Widget> children = const <Widget>[],
})其他构造函数
// 适合列表项比较多或者列表项不确定的情况
ListView.builder({
...
// 它是列表项的构建器,类型为IndexedWidgetBuilder
// (BuildContext context, int index)返回值为一个widget
required IndexedWidgetBuilder itemBuilder,
// 列表项的数量,如果为null,则为无限列表
int itemCount,
...
})
// 需要额外分隔时可使用的builder构造函数
ListView.separated({
...
required IndexedWidgetBuilder itemBuilder,
// 分隔生成器
required IndexedWidgetBuilder separatorBuilder,
int itemCount,
...
})7.1.4 AnimatedList
AnimatedList和ListView的功能大体相似,不同的是,AnimatedList可以在列表中插入或删除节点时执行一个动画,在需要添加或删除列表项的场景中会提高用户体验- 在
build函数中,增加了一个动画参数用于添加时出现动画效果
7.1.5 GridView
- 网格布局是一种常见的布局类型,GridView组件正是实现了网格布局的组件
GridView({
Key? key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController? controller,
bool? primary,
ScrollPhysics? physics,
bool shrinkWrap = false,
EdgeInsetsGeometry? padding,
// SliverGridDelegate抽象类型,它的作用是控制GridView子组件如何排列(layout)
required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double? cacheExtent,
List<Widget> children = const <Widget>[],
...
})
// 两个SliverGridDelegate的子类
// 横轴为固定数量子元素
// SliverGridDelegateWithFixedCrossAxisCount
SliverGridDelegateWithFixedCrossAxisCount({
// crossAxisCount:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商
@required double crossAxisCount,
// mainAxisSpacing:主轴方向的间距
double mainAxisSpacing = 0.0,
// crossAxisSpacing:横轴方向子元素的间距
double crossAxisSpacing = 0.0,
// childAspectRatio:子元素在横轴长度和主轴长度的比例(宽高比)。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度
double childAspectRatio = 1.0,
})
// 横轴子元素为固定最大长度
// SliverGridDelegateWithMaxCrossAxisExtent
SliverGridDelegateWithMaxCrossAxisExtent({
// maxCrossAxisExtent为子元素在横轴上的最大长度,之所以是“最大”长度,是因为如果剩余一些空间,此时GridView将会缩小每一个Item长度多放一个
double maxCrossAxisExtent,
// 其他参数和SliverGridDelegateWithFixedCrossAxisCount相同
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})GridView的其他构造函数
// GridView.count构造函数内部使用了SliverGridDelegateWithFixedCrossAxisCount
// 我们通过它可以快速的创建横轴固定数量子元素的GridView
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
// GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent
// 我们通过它可以快速的创建横轴子元素为固定最大长度的GridView
GridView.extent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0,
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
// GridView构造器
// 用于动态构建子widget
// 比如分批异步获取数据,itemBuilder可以获取到当前需要渲染的是第几个子元素,可以借此判断是否需要获取新的数据
GridView.builder(
...
required SliverGridDelegate gridDelegate,
required IndexedWidgetBuilder itemBuilder,
)7.1.6 PageView
- 如果要实现页面切换和
Tab布局,我们可以使用PageView组件,使用PageView,可以让多页面支持左右(或上下)滚动切换
7.1.7 滚动监听与控制
- 可以通过
ScrollController对滚动进行监听和控制,比如实现路由切换时保存位置等
