前言
在上一篇文章中,我们了解了Flutter中的视窗和Sliver
,并通过案例知道了CustomScrollView
、SliverList
、SliverGrid
、SliverAppBar
等常用的Sliver系列组件。那么现在让我们来探索更多常用的Slivers吧!
SliverToBoxAdapter
如果想在CustomScrollView
中添加SizedBox
、Row
这样基于Box
协议的组件,你会发现不能直接添加,因为在CustomScrollView
中需要使用Sliver
协议来实现一些东西,这时也许你会Google:我应该如何将Box协议的组件更改为Sliver协议的组件?答案是:使用SliverToBoxAdapter
即可。
— 在Flutter中,主要有两种布局协议:box协议,和sliver协议。
SliverToBoxAdapter
只是一个用于包裹Box
协议的Sliver组件。如果你想在CustomScrollView
中显示一个子项时,通过它会变得非常方便。
Scaffold(
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: _helloText,
)
],
),
);
Widget get _helloText {
return Column(
children: [
...List.generate(
10, (index) => Text("Taxze SliverToBoxAdapter $index")),
ElevatedButton(
onPressed: () => print("Say Hello!"), child: Text("Hello"))
],
);
}
但是很多朋友第一次使用该组件时,会出现使用SliverToBoxAdapter
去包裹可滚动组件的情况,例如:
SliverToBoxAdapter(
child: ListView(
children: [],
),
)
出现与sliver
滑动方向一致的情况时,这个ListView
便没有办法正常工作。
SliverPersistentHeader
在上一篇文章中已经知道了SliverAppBar
有很多控制属性,但如果想要控制更多的SliverAppBar
的行为或自定义AppBar
,又或者想要在列表某处固定一个Item,那么可以使用SliverPersistentHeader
。
SliverPersistentHeader
有一个必传参数和两个可选参数:
- pinned: 默认为false,用于控制item是否固定。例如将自定义的AppBar贴在顶部。
- floating:默认为false,用于控制item是否浮动。例如向下滑动时,自定义的AppBar会展开。
- delegate:必须传入的参数。该参数需要一个实现扩展抽象类的委托类
SliverPersistentHeaderDelegate
。
现在就来实现一个TaxzePersistentHeaderDelegate
,来体验SliverPersistentHeader
的强大吧!
可以看到有四个需要实现的方法:
- build 需要构建渲染的内容,其中shrinkOffset参数的取值为[0,maxExtent],当header在顶部时,值为0
- maxExtent 展开时组件的高度
- minExtent 收起时组件的高度
- shouldRebuild 判断Header是否需要重新构建,通常在父级状态更新时触发
class TaxzePersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
TaxzePersistentHeaderDelegate({
required this.minSize,
required this.maxSize,
});
final double minSize;
final double maxSize;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
print(shrinkOffset);
return Stack(
fit: StackFit.expand,
children: [
...
],
);
}
@override
bool shouldRebuild(TaxzePersistentHeaderDelegate oldDelegate) =>
oldDelegate.maxExtent != maxExtent || oldDelegate.minExtent != minExtent;
@override
double get maxExtent => maxSize;
@override
double get minExtent => minSize;
}
pinned
属性和floating
属性就不过多介绍了,现在通过一个实例,来看看SliverPersistentHeader
能实现什么样的常用功能。效果图:
实现起来也很简单,第一步可以先封装一个通用的HeaderDelegate
,方便快速构建 SliverPersistentHeaderDelegate
,减少重复代码。
typedef SliverHeaderBuilder = Widget Function(
BuildContext context, double shrinkOffset, bool overlapsContent);
class TaxzePersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
TaxzePersistentHeaderDelegate({
this.minSize = 0,
required this.maxSize,
required Widget child,
}) : builder = ((context, shrinkOffset, overlapsContent) => child),
assert(minSize <= maxSize && minSize >= 0);
final double minSize;
final double maxSize;
final SliverHeaderBuilder builder;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
Widget child = builder(context, shrinkOffset, overlapsContent);
//高度充满父约束,高度在[minHeight,maxHeight]之间变化
return SizedBox.expand(
child: child,
);
}
@override
bool shouldRebuild(TaxzePersistentHeaderDelegate oldDelegate) =>
oldDelegate.maxExtent != maxExtent || oldDelegate.minExtent != minExtent;
@override
double get maxExtent => maxSize;
@override
double get minExtent => minSize;
}
使用起来也很简单:
SliverPersistentHeader(
pinned: true,
delegate: TaxzePersistentHeaderDelegate(
maxSize: 100,
minSize: 60,
child: buildItemHeader(1),
),
),
Widget buildItemHeader(int i) {
return Container(
...
);
}
额外的知识点:SliverPersistentHeader
组件的设计初衷主要是为了实现 SliverAppBar
,所以SliverAppBar
原理其实就像是这样:
CustomScrollView(
slivers: [
SliverToBoxAdapter(),
//防止SliverPersistentHeader成为最顶层的Sliver,以至于无法上拉刷新
SliverPersistentHeader(
delegate:XXXDelegate(),
//固定在顶部
pinned: true,
)
],
),
SliverFixedExtentList
SliverFixedExtentList
与SliverList
用法一样,唯一的区别是SliverFixedExtentList
是固定子组件的高度的,所以如果你确定了子组件的高度,那么请选择SliverFixedExtentList
,因为它无需计算子组件的布局尺寸,更加高效!而且如果想要跳转到很远的距离时,在加载组件之前就知道尺寸是非常重要的!例如:每个item固定为100px,现在要往下滚动2000px,那么只需要跳转20个item,只需要加载第21个item即可。如果子组件的尺寸是不固定的,那么如果想要跳转到2000px的地方,就不得不逐个渲染中间的组件,才能知道2000px的位置。
CustomScrollView(
slivers: [
SliverFixedExtentList(
//固定尺寸
itemExtent: 100,
delegate: SliverChildBuilderDelegate((ctx, index) {
return Container(
alignment: Alignment.center,
color: Colors.primaries[index % Colors.primaries.length],
child: Text(
"$index",
style: const TextStyle(color: Colors.white, fontSize: 18),
),
);
}),
)
],
),
SliverPrototypeExtentList
知道了子组件的尺寸就能使用SliverFixedExtentList
从而提高性能,但是在真实的业务中,子组件固定尺寸的场景较少,一般都是出现在设置页,想要在其他的场景下固定子组件的尺寸是非常麻烦的,但是,有了SliverPrototypeExtentList
就简单多了!
为什么说使用它会变得简单呢?在SliverPrototypeExtentList
中,有一个prototypeItem
属性,可以传入一个组件,但是这个组件不会被渲染到屏幕上,该组件的作用是提供一个尺寸,在SliverPrototypeExtentList
中的组件尺寸都会被设置成该组件的尺寸。
SliverPrototypeExtentList(
prototypeItem: const Text(""),
delegate: SliverChildBuilderDelegate((ctx, index) {
return Text("$index");
}),
)
SliverFillViewport
学习这个组件之前,让我们回想一下PageView
组件,PageView
每一个子组件都会占据整个父约束,然后可以滑动切换。而在Sliver
中,就有SliverFillViewport
,它也是PageView
的底层原理。在PageView
的build
函数中就可以看到SliverFillViewport
。
SliverFillViewport(
delegate: SliverChildListDelegate([
Container(
color: Colors.red,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.green,
),
]),
),
SliverFillRemaining
SliverFillRemaining
与SliverFillViewport
很类似,SliverFillViewport
是生成的每一个item都占满全屏,而SliverFillRemaining
是会自动填充满整个视图。SliverFillRemaining
有两个属性:
- hasScrollBody 默认为true,该输入用于判断内容是否可以滚动。
- fillOverscroll 允许在 iOS 上看到列表过度滚动时的拉伸行为。
CustomScrollView(
slivers: [
SliverList(delegate: SliverChildBuilderDelegate((ctx, index) {
return Container(
height: 50,
margin: EdgeInsets.all(10),
color: Colors.red,
);
},childCount: 5)),
SliverFillRemaining(
hasScrollBody: true,
child: FlutterLogo(),
)
],
),
hasScrollBody:true |
hasScrollBody:false |
SliverPadding
如果想在CustomScrollView
中添加Padding
时,在没有了解过SliverPadding
之前,也许你会想使用SliverToBoxAdapter
去包裹一层Padding
,但是其实不用那么麻烦,Slivers中就有SliverPadding
这样的组件帮助实现需求。使用它也很简单,只需要在需要Padding
的Sliver
组件外报上一层即可。
SliverPadding(
padding: EdgeInsets.all(20.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate((ctx, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
height: 50,
);
},childCount: 10),
),
)
— 在2020 年 9 月 29 日,修复了之前使用SliverPersistentHeader
外面包裹SliverPadding
导致SliverPersistentHeader
的pinned
属性失效的问题,有兴趣的可以看下这个。
关于我
Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 ,也可以通过掘金的新的私信功能联系到我。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝