在开发的过程中比较常用的是ListView和GridView,但是如果是复杂一些的页面,单纯的ListView和GridView就不够用了。而Flutter提供了Sliver系列组件来实现复杂的滚动页面,这里就是学习的过程中所作的记录,都是入门级别的,比较简单。
Demo地址位于文章末尾。
下面就开始正文了。
SliverList和SliverGrid
SliverList只有一个属性:delegate,类型是SliverChildDelegate。SliverChildDelegate是一个抽象类,不能直接使用。Flutter中定义好了两个继承于SliverChildDelegate的类对象,可以直接用,分别是:SliverChildListDelegate 和SliverChildBuilderDelegate。
先来看SliverChildListDelegate,声明如下:
SliverChildListDelegate(
this.children, {
...
})
只有一个必填属性children,是一个类型为Widget的List集合。其他属性几乎不用,暂时忽略。跟ListView构造函数相同,会将所有的子组件一次性的全部渲染出来。
SliverChildBuilderDelegate则跟ListView.build构造函数类似,需要时才会创建,提高了性能。
const SliverChildBuilderDelegate(
this.builder, {
this.childCount,
...
})
主要参数是builder,是一个返回值为Widget的函数,原型如下:
Widget Function(BuildContext context, int index)
这个比较简单就不记录了。
SliverFixedExtentList是固定item高度的SliverList,只是比SliverList多了一个参数itemExtent来设置item高度,其用法跟SliverList一致。
SliverGrid有两个属性:delegate和gridDelegate。其中delegate跟上面的用法一样,gridDelegate的类型是SliverGridDelegate。
SliverGridDelegate是一个抽象类,定义了子控件Layout相关的接口。Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,可以直接使用。下面分别介绍一下。
SliverGridDelegateWithFixedCrossAxisCount
该类实现了一个横轴方向上固定子控件数量的layout的算法,构造函数为:
SliverGridDelegateWithFixedCrossAxisCount({
@required double crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
-
crossAxisCount
:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即横轴长度除以crossAxisCount的商。 -
mainAxisSpacing
:主轴方向的间距。 -
crossAxisSpacing
:横轴方向子元素的间距。 -
childAspectRatio
:子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在竖轴的长度。
子元素的大小是通过crossAxisCount和childAspectRatio两个参数决定的。
SliverGridDelegateWithMaxCrossAxisExtent
该类实现了一个横轴方向上子元素为固定最大长度的layout算法,其构造函数为:
SliverGridDelegateWithMaxCrossAxisExtent({
double maxCrossAxisExtent,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
maxCrossAxisExtent
是子元素在横轴方向上的最大长度。之所以是最大长度而不是最终长度,这是因为子元素在横轴方向上的长度仍然是等分的。所以子元素在横轴方向上的元素个数num = 横轴长度 / maxCrossAxisExtent + 1,最终的子元素的宽度= 横轴长度 / num。其他参数和SliverGridDelegateWithFixedCrossAxisCount相同。
SliverGrid的使用方法跟GridView的使用方法保持一致。
SliverAnimatedList
SliverAnimatedList是带有动画的SliverList,先来看构造函数:
SliverAnimatedList({
Key key,
@required this.itemBuilder,
this.initialItemCount = 0,
})
-
initialItemCount:item
的个数。 -
itemBuilder
:是一个AnimatedListItemBuilder
函数,原型如下:
Widget Function(BuildContext context, int index, Animation<double> animation)
使用SliverAnimatedList在添加或删除item的时候,需要通过一下方式来操作:
- 定义一个GlobalKey
GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>();
- 将key赋值给SliverAnimatedList
SliverAnimatedList(
key: _listKey,
initialItemCount: _list.length,
itemBuilder: _buildItem,
)
- 通过key.currentState.insertItem或key.currentState.removeItem来进行添加或删除。
_listKey.currentState.insertItem(_index);
_listKey.currentState.removeItem(_index,
(context, animation) => _buildItem(context, item, animation));
_buildItem
函数原型如下:
Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
'Item $index',
),
),
),
);
}
如果想修改动画类型,就需要修改_buildItem
中的动画方式。
SliverPersistentHeader
这个组件可以实现控件吸顶的效果。先来看构造函数:
SliverPersistentHeader({
Key key,
@required this.delegate,
this.pinned = false,
this.floating = false,
})
其中pinned
的效果就是控制header是否保持吸顶效果。另一个重要的属性则是delegate
,它的类型是SliverPersistentHeaderDelegate
,这是一个抽象类,所以要使用的话,需要自己定义一个子类。
子类需要重写4个父类的函数:
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
// TODO: implement build
throw UnimplementedError();
}
@override
// TODO: implement maxExtent
double get maxExtent => throw UnimplementedError();
@override
// TODO: implement minExtent
double get minExtent => throw UnimplementedError();
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
// TODO: implement shouldRebuild
throw UnimplementedError();
}
其中build
返回header显示的内容,maxExtent
和minExtent
表示最大值和最小值,即header展开和闭合时的高度,若相同则header高度保持不变,若不同,则滚动的时候header的高度会自动在两只之间进行变化。shouldRebuild
表示是否需要重新绘制,需要的话则返回true。
代码如下:
CustomScrollView(
slivers: <Widget>[
// makeHeader('Header Section 1'),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(// 自定义的delegate
minHeight: 60.0,
maxHeight: 60.0,
child: Container(
color: Colors.white,
child: Center(
child: Text('Header Section 1'),
),
),
),
),
SliverGrid.count(
crossAxisCount: 3,
children: [
Container(color: Colors.red, height: 150.0),
Container(color: Colors.purple, height: 150.0),
Container(color: Colors.green, height: 150.0),
Container(color: Colors.orange, height: 150.0),
Container(color: Colors.yellow, height: 150.0),
Container(color: Colors.pink, height: 150.0),
Container(color: Colors.cyan, height: 150.0),
Container(color: Colors.indigo, height: 150.0),
Container(color: Colors.blue, height: 150.0),
],
),
],
)
通过SliverPersistentHeader可以实现SliverAppBar的效果。
SliverAppBar
先来看构造函数:
SliverAppBar({
this.flexibleSpace,
this.forceElevated = false,
this.expandedHeight,
this.floating = false,
this.pinned = false,
this.snap = false,
this.stretch = false,
this.stretchTriggerOffset = 100.0,
this.onStretchTrigger,
...
})
其他的参数都跟AppBar是一致的,就忽略了。其中有一些重要的参数:
-
expandedHeight
:展开时AppBar的高度。 -
flexibleSpace
:空间大小可变的组件。 -
floating
:向上滚动时,AppBar会跟随着滑出屏幕;向下滚动时,会有限显示AppBar,只有当AppBar展开时才会滚动ListView。 -
pinned
:当SliverAppBar内容滑出屏幕时,将始终渲染一个固定在顶部的收起状态AppBar。 -
snap
:当手指离开屏幕时,AppBar会保持跟手指滑动方向相一致的状态,即手指上滑则AppBar收起,手指下滑则AppBar展开。 -
stretch
:是否拉伸。
只有floating设置为true时,snap才可以设置为true。
FlexibleSpaceBar
是Flutter提供的一个现成的空间大小可变的组件,并且处理好了title和background的过渡效果。重点说一下其中的stretchModes
属性,这个是用来设置AppBar拉伸效果的,有三个枚举值,可以互相搭配使用,但是前提是stretch
为true。
-
blurBackground
:拉伸时使用模糊效果。 -
fadeTitle
:拉伸时标题将消失。 -
zoomBackground
:默认值,拉伸时widget将填充额外的空间。
SliverFillRemaining和SliverFillViewport
SliverFillRemaining会自动充满视图的全部空间,通常用于slivers的最后一个。
SliverFillViewport 生成的每一个item都占满全屏。
用法都比较简单,就没有记录。
详细可查看demo。
SliverOpacity和SliverPadding
SliverOpacity用来设置子控件透明度,构造函数如下:
SliverOpacity({
Key key,
@required this.opacity,
this.alwaysIncludeSemantics = false,
Widget sliver,
})
SliverPadding是设置需要padding的Sliver控件,其构造函数如下:
SliverPadding({
Key key,
@required this.padding,
Widget sliver,
})
SliverOpacity 和SliverPadding 中的sliver属性的值必须是Sliver系列的Widget。
SliverPrototypeExtentList
跟SliverList用法基本一致,但是子控件的高度是由prototypeItem的控件高度决定的。构造函数如下:
SliverPrototypeExtentList({
Key key,
@required SliverChildDelegate delegate,
@required this.prototypeItem,
})
SliverLayoutBuilder
理解有限,就不记录贻笑大方了。
可查看大佬的文章: Flutter Sliver一辈子之敌 (ExtendedList)
如果在CustomScrollView中用到了其他非Sliver系列的组件,需要使用SliverToBoxAdapter将这些组件包裹起来。
参考文章如下:
1、在Flutter中创建有意思的滚动效果 - Sliver系列
2、Slivers, Demystified
Demo地址:点击跳转