0
点赞
收藏
分享

微信扫一扫

Flutter学习 可滚动Widget 中


文章目录

  • ​​5. AnimatedList​​
  • ​​5.1 实例代码​​
  • ​​6. GridView​​
  • ​​6.1 SliverGridDelegateWithFixedCrossAxisCount​​
  • ​​6.2 GridView.count​​
  • ​​6.3 SliverGridDelegateWithMaxCrossAxisExtent​​
  • ​​6.4 GridView.extent​​
  • ​​6.5 GridView.builder​​
  • ​​6.5.1 范例​​
  • ​​7. PageView 与 页面缓存​​
  • ​​7.1 PageView​​
  • ​​7.2 页面缓存​​
  • ​​8. 可滚动组件子项缓存 KeepAlive​​
  • ​​8.1 AutomaticKeepAlive​​
  • ​​9. TabBarView​​
  • ​​9.1 TabBarView​​
  • ​​9.2 TabBar​​
  • ​​9.3 示例​​
  • ​​10. CustomScrollView 和 Slivers​​
  • ​​10.1 CustomScrollView​​
  • ​​10.2 Flutter 中常用的 Sliver​​
  • ​​10.2.1 示例​​
  • ​​10.2.2 SliverToBoxAdapter​​
  • ​​10.2.3 SliverPersistentHeader​​

5. AnimatedList

​AnimatedList​​ 和 ListView 功能差不多, 顾名思义,它在列表中插入节点或删除节点时会执行一些动画

它是一个 ​​StatefulWidget​​​ ,对应的 State 是 ​​AnimatedListState​​,添加、删除元素的方法是:

void insetItem(int index, { Duration duration = mDuration });
void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = mDuration });

5.1 实例代码

class _AnimatedListRouteState extends State<AnimatedListRoute> {
var data = <String>[];
int counter = 5;

final globalKey = GlobalKey<AnimatedListState>();

@override
void initState() {
for (var i = 0; i < counter; i++) {
data.add("${i + 1}");
}
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
AnimatedList(
key: globalKey,
initialItemCount: data.length,
itemBuilder: (BuildContext context, int index,
Animation<double> animation) {
// 添加列表项时会执行渐显动画
return FadeTransition(
opacity: animation, child: buildItem(context, index));
}),
// 创建一个添加按钮
buildAddBtn(),
],
),
);
}

Widget buildAddBtn() {
return Positioned(
child: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// 添加一个列表项
data.add("${++counter}");
// 告诉列表项有添加的列表项
globalKey.currentState!.insertItem(data.length - 1);
},
),
bottom: 30,
left: 0,
right: 0,
);
}

// 构建列表项
Widget buildItem(context, index) {
String char = data[index];
return ListTile(
key: ValueKey(char),
title: Text(char),
trailing: IconButton(
icon: const Icon(Icons.delete),
// 点击时进行删除
onPressed: () => onDelete(context, index)),
);
}

void onDelete(context, index) {
setState(() {
globalKey.currentState!.removeItem(index, (context, animation) {
// 删除过程执行的是反向动画, animation.value 会从 1 变成0
var item = buildItem(context, index);
data.removeAt(index);
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
),
// 不断缩小列表项的高度
child: SizeTransition(
sizeFactor: animation,
axisAlignment: 0.0,
child: item,
),
);
}, duration: const Duration(milliseconds: 200));
});
}
}

6. GridView

GridView 用于构建网格列表,构造函数如下:

class GridView extends BoxScrollView {
GridView({
Key? key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController? controller,
bool? primary,
ScrollPhysics? physics,
bool shrinkWrap = false,
EdgeInsetsGeometry? padding,
required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double? cacheExtent,
List<Widget> children = const <Widget>[],
...
}

GridView 也包含了大多数的 ListView 有的通用参数,比较重要的是 ​​gridDelegate​​​ 这个属性,它接受一个 ​​SliverGridDelegate​​​,控制 ​​GridView​​ 子组件如何排列

  • ​SliverGridDelegate​​​ 是一个抽象类, 定义了 GridView Layout相关接口,子类实现它来实现布局算法。 Flutter已经提供两个实现类,分别是:​​SliverGridDelegateWithFixedCrossAxisCount​​ 和 ​​SliverGridDelegateWithMaxCrossAxisExtent​

这两个用的应该会比较多,我们来使用并介绍它:

6.1 SliverGridDelegateWithFixedCrossAxisCount

横轴为固定数量子元素的布局算法,构造函数为:

SliverGridDelegateWithFixedCrossAxisCount({
required this.crossAxisCount,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
this.mainAxisExtent,
}

  • ​crossAxisCount​​​ 横轴子元素数量, 此属性确定后, 子元素在横轴的长度就确定了,即 ​​ViewPort 横轴长度 / crossAxisCount​
  • ​mainAxisSpacing​​ 主轴方向的间距
  • ​crossAxisSpacing​​ 横轴方向子元素的间距
  • ​childAspecRatio​​ 子元素在横轴长度和主轴长度的比例, 用于 crossAxisCount 指定后,子元素横轴长度就确定了,然后通过此参数值可以确定子元素在主轴的长度
  • ​mainAxisExtent​​ 主轴上每个子元素的具体长度

这里看一个例子:

GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// 横轴三个子Widget
crossAxisCount: 4,
// 宽高比为 1:1
childAspectRatio: 1.0),
children: const [
Icon(Icons.add),
Icon(Icons.eleven_mp),
Icon(Icons.ten_k),
Icon(Icons.cake),
Icon(Icons.beach_access),
Icon(Icons.free_breakfast),
Icon(Icons.all_inclusive),
],
))

Flutter学习 可滚动Widget 中_缓存

6.2 GridView.count

GridView.count 构造函数内部使用了 ​​SliverGridDelegateWithFixedCrossAxisCount​​ ,我们通过它可以快速的创建横轴固定数量子元素的 GridView, 上面的示例代码其实就等价于:

GridView.count(
crossAxisCount: 4,
childAspectRatio: 1.0,
children: const [
Icon(Icons.add),
Icon(Icons.eleven_mp),
Icon(Icons.ten_k),
Icon(Icons.cake),
Icon(Icons.beach_access),
Icon(Icons.free_breakfast),
Icon(Icons.all_inclusive),
],
)

6.3 SliverGridDelegateWithMaxCrossAxisExtent

实现了横轴子元素为固定最大长度的布局算法,构造函数为:

const SliverGridDelegateWithMaxCrossAxisExtent({
required this.maxCrossAxisExtent,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
this.mainAxisExtent,
}

属性和之前所学基本一模一样,而 ​​maxCrossAxisExtent​​​ 为子元素在横轴上的最大长度, 如果 ViewPort 的横轴长度是 450,那么当 ​​maxCrossAxisExtent​​​ 的值在区间 [450/4, 450/3] 的话,子元素最终实际长度都是 112.5 而 ​​childAspectRatio​​ 是指子元素横轴和主轴的长度比

下面看一个例子:

GridView(
padding: EdgeInsets.zero,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0, childAspectRatio: 2.0 // 宽高比为2
),
children: const [
Icon(Icons.add),
Icon(Icons.eleven_mp),
Icon(Icons.ten_k),
Icon(Icons.cake),
Icon(Icons.beach_access),
Icon(Icons.free_breakfast),
Icon(Icons.all_inclusive),
],
),

Flutter学习 可滚动Widget 中_flutter_02

6.4 GridView.extent

上面的代码等价于:

GridView.extent(
padding: EdgeInsets.zero,
maxCrossAxisExtent: 120.0, childAspectRatio: 2.0,
// 宽高比为2
children: const [
Icon(Icons.add),
Icon(Icons.eleven_mp),
Icon(Icons.ten_k),
Icon(Icons.cake),
Icon(Icons.beach_access),
Icon(Icons.free_breakfast),
Icon(Icons.all_inclusive),
],
),

6.5 GridView.builder

上面我们介绍 GridView 都需要一个 widget 数组作为其子元素,这些方式都会提前将所有子 widget 都构建好,所以只适用于子Widget数量比较少的时候,当使用较多的时候,和 ListView一样, 使用 ​​GridView.builder​​ 来构建子 Widget, GridView.builder 必须指定两个参数:

GridView.builder(
...
required SliverGridDelegate gridDelegate,
required IndexedWidgetBuilder itemBuilder,
)

6.5.1 范例

class _GridViewRouteRouteState extends State<GridViewRoute> {
// icon 数据源
List<IconData> _icons = [];

@override
void initState() {
super.initState();
// 初始化数据
_retrieveIcons();
}

// 模拟异步加载数据
void _retrieveIcons() {
Future.delayed(const Duration(milliseconds: 200)).then((value) {
setState(() {
_icons.addAll([
Icons.add,
Icons.eleven_mp,
Icons.ten_k,
Icons.cake,
Icons.beach_access,
Icons.free_breakfast,
Icons.all_inclusive
]);
});
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// 每行4列
crossAxisCount: 3,
childAspectRatio: 1.0),
itemCount: _icons.length,
itemBuilder: (context, index) {
if (index == _icons.length -1 && _icons.length < 200) {
_retrieveIcons();
}
return Icon(_icons[index]);
}),
);
}
}

Flutter学习 可滚动Widget 中_ico_03

7. PageView 与 页面缓存

7.1 PageView

在Android中,如果需要实现页面的切换,可以使用 ​​PageView​​,Flutter也有同名同作用的 Widget, 下面是它的构造函数:

PageView({
Key? key,
this.scrollDirection = Axis.horizontal,
this.reverse = false,
PageController? controller,
this.physics,
this.pageSnapping = true,
this.onPageChanged,
List<Widget> children = const <Widget>[],
this.dragStartBehavior = DragStartBehavior.start,
this.allowImplicitScrolling = false,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.scrollBehavior,
this.padEnds = true,
}

  • ​pageSnapping​​ 每次滑动是否强制切换整个画面,如果为false,会根据实际的滑动距离显示页面
  • ​this.allowImplicitScrolling​​ 主要是配合辅助功能使用
  • ​padEnds​​ 下面会讲解

我们看一个 Tab 切换的实例,每个Tab都只显示一个数字,然后总共有6个Tab:

class _PageViewRouteState extends State<PageViewRoute> {
@override
Widget build(BuildContext context) {
var children = <Widget>[];
// 设置六个Tab
for (int i = 0; i < 6; i++) {
children.add(Page(
text: "$i",
));
}
return Scaffold(body: PageView(children: children));
}
}

class Page extends StatefulWidget {
const Page({Key? key, required this.text}) : super(key: key);

final String text;

@override
State<StatefulWidget> createState() => _PageState();
}

class _PageState extends State<Page> {
@override
Widget build(BuildContext context) {
print("build ${widget.text}");
return Center(
child: Text(
widget.text,
textScaleFactor: 5,
));
}
}

接下来就可以正常滑动Tab了,实现还是比较简单的

7.2 页面缓存

在上面的示例中,每次页面的切换,可都会触发新 Page 页的 ​​build​​,这说明 PageView 默认没有缓存功能,一旦某个Page画出页面就会被销毁

这是因为 PageView 没有透传 cacheExtent 给 Viewport,所以 Viewport 默认 cacheExtent为1, 但是却在 ​​allowImplicitScrolling​​​ 为 true 时设置了预渲染区域, 此时将会设置缓存类型为 ​​CacheExtentStyle.viewport​​​ ,则 cacheExtent 则表示缓存的长度是几个 Viewport 的宽度, cacheExtent 为1.0,则代表前后各缓存一页。
也就是说,将 PageView 的 ​​​allowImplicitScrolling​​ 设置为 true 时,就会缓存前后两页的Page

问题的根源貌似是 在 PageView 中设置 cacheExtent 会和 iOS的辅助功能有冲突,没有更好的解决方法,Flutter就带着这个问题。但是国内基本不用考虑用辅助功能,所以想到的解决方案就是 拷贝一份PageView 的源码,然后透传 cacheExtent 即可。

当然,Flutter还提供了更通用的解决方案,就是缓存子项的解决方案

8. 可滚动组件子项缓存 KeepAlive

在 ListView 的构造函数中有一个 ​​addAutomaticKeepAlives​​​ 属性没有介绍,如果为 true, ListView就会为其每一个子项添加一个 ​​AutomaticKeepAlive​​​ 父组件。 虽然 PageView 的默认构造函数和 PageView.build 构造函数中没有该参数,但他们最终都会生成一个 ​​SliverChildDelegate​​​,这个组件会在每个列表子项构建完成时,为其添加一个 ​​AutomaticKeepAlive​​ 的父组件,下面来介绍一个这个父组件。

8.1 AutomaticKeepAlive

AutomaticKeepAlive 组件的主要作用是将列表项的 根RenderObject 的 ​​keepAlive​按需自动标记为true或false。
就是 列表项的根 Widget, 这里将 Viewport+cacheExtent称为 加载区域

  1. 当 keepAlive 为false时,如果列表项滑出加载区域,列表组件会被销毁
  2. 当 keepAlive 为true时,当列表项滑出加载区域后,Viewport会将列表项组件缓存起来,当列表项进入加载区域时,Viewport先从缓存中查找是否已经缓存,如果有直接复用,如果没有重新创建列表项

而 AutomaticKeepAlive 这个标识位的设置,其实全靠我们开发者来控制的。

所以为了让 PageView 实现多页面的缓存,我们的思路就是让 PageView 来讲 AutomaticKeepAlive 置为true,Flutter 的做法就是让列表项组件 混入一个 ​​AutomaticKeepAliveClientMixin​​​,然后实现 ​​wantKeepAlive()​​ 就可以了,代码如下:

class _PageState extends State<Page> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
...
}

@override
bool get wantKeepAlive => true; // 需要缓存
}

在实现了 ​​wantKeepAlive​​​ 后,还必须要在 ​​build​​​ 方法中调用一下 ​​super.build(context)​​,它会将 keepAlive 的信息通知出去。

需要注意的是,如果我们采用 ​​PageView.cutom​​ 构建页面时,没有给列表项包装 AutomaticKeepAlive 父组件,则上述方案不能正常工作。

9. TabBarView

TabBarView 是 Marterial 提供的 Tab 组件,通常和 TabBar 搭配使用

9.1 TabBarView

TabBarView 封装了 PageView,构造函数如下:

const TabBarView({
Key? key,
required this.children, // Tab 页面
this.controller, // TabController
this.physics,
this.dragStartBehavior = DragStartBehavior.start,
})

这里的 ​​TabController​​ 是用来监听和控制 TabBarView 的页面切换,通常是和 TabBar 来联动的,如果没有指定会默认在组件树上查找最近一个使用的 DefaultTabController

9.2 TabBar

TabBar 的许多属性都是用来配置 指示器 和 label 的,我们来看下其构造函数:

const TabBar({
Key? key,
// 具体的 Tab 数组,需要我们来创建
required this.tabs,
// TabController,用于和 TabBarView 联动
this.controller,
// 是否可以滑动
this.isScrollable = false,
this.padding,
// 指示器颜色
this.indicatorColor,
this.automaticIndicatorColorAdjustment = true,
// 指示器高度,默认为2
this.indicatorWeight = 2.0,
this.indicatorPadding = EdgeInsets.zero,
// 指示器 Decoration
this.indicator,
// 指示器长度,两个可选值,一个是 Tab 长度,一个是 label 长度
this.indicatorSize,
this.labelColor,
this.labelStyle,
this.labelPadding,
...
})

TabBar 和 TabBarView 是靠 TabController 进行联动的, 需要注意的是, TabBar 和 TabBarView 的孩子数量需要一致。

tab 是 TabBar 的孩子,可以是任意 Widget, 不过 Material 已经实现了默认的 Tab 组件给我们使用:

const Tab({
Key? key,
this.text, //文本
this.icon, // 图标
this.iconMargin = const EdgeInsets.only(bottom: 10.0),
this.height,
this.child, // 自定义 widget
})

其中 text 和 child 是互斥的

9.3 示例

代码如下:

class _TabBarViewRouteState extends State<TabBarViewRoute>
with SingleTickerProviderStateMixin {
final ScrollController _controller = ScrollController();

late TabController _tabController;
List tabs = ["吃的", "穿的", "住的", "行的"];

@override
void initState() {
super.initState();
_tabController = TabController(length: tabs.length, vsync: this);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("TabBarView"),
bottom: TabBar(
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList(),
)),
body: TabBarView(
controller: _tabController,
children: tabs.map((e) {
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5),
);
}).toList(),
));
}

@override
void dispose() {
// 释放资源
_tabController.dispose();
super.dispose();
}
}

Flutter学习 可滚动Widget 中_缓存_04


由于 TabController 又需要一个 ​​TickerProvider​​​ (vsync 参数),所以我们又混入了 ​​SingleTickerProviderStateMixin​​,由于 TabController 会执行动画,持有一些资源,所以我们在页面销毁时需要释放资源(dispose)

综上,我们发现创建 TabController 的过程还是比较复杂的,实战中,如果需要 TabBar 和 TabBarView 联动,通常会创建一个 DefaultTabController 作为它们的共同父级组件,这样它们在执行的时候就会从组件向上查找,都会使用我们指定的这个 Controller。代码如下:

return DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
title: const Text("TabBarView"),
bottom: TabBar(
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList(),
)),
body: TabBarView(
controller: _tabController,
children: tabs.map((e) {
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5),
);
}).toList(),
)));
}

这样我们就不需要手动去管理 Controller 的生命周期(无需手动释放),也不需要混合 SingleTickerProviderStateMixin 了。

10. CustomScrollView 和 Slivers

10.1 CustomScrollView

前面学习的 ListView 、PageView、 GridView 都是完整的可滚动组件,这是因为它们都包含了 Scrollable、 Viewport、Sliver 三大要素。

假如我们想要在一个页面中,同时包含多个可滚动组件,且使他们的效果能够统一起来,比如,想要将两个沿垂直方向滚动的 ListView 合成一个 ListView, 在第一个 ListView 滑动到底部的时候能够接上第二个 ListView, 先尝试下一下代码:

class _CustomViewRouteState extends State<CustomViewRoute> {
@override
Widget build(BuildContext context) {
return Scaffold(body: buildTwoListView());
}

Widget buildTwoListView() {
var listView = ListView.builder(
itemBuilder: (_, index) => ListTile(title: Text("$index")),
itemCount: 20);

return Column(
children: [
Expanded(child: listView,),
const Divider(color: Colors.grey),
Expanded(child: listView)
],
);
}

Flutter学习 可滚动Widget 中_ide_05


页面中有两个 ListView,各占一半,虽然能够显示且滑动,但是每个 ListView 只会响应自己可视区域中的滑动,实现不了我们想要的效果。 之所以这样的原因是两个 ListView 都有自己独立 Scrollable、 Viewport、Sliver

所以,我们需要给他们创建共用的 Scrollable、 Viewport 对象,然后将两个 ListView 对应的 Sliver 添加到这个共用的 Viewport 对象中就可以实现想要的效果了。 但是实现起来无疑是很复杂的, 所以 Flutter 提供了一个 ​​CustomScrollView​​ 来帮助创建公共的 Scrollable 和 Viewport,然后接受一个 Sliver 数组,代码如下:

buildTwoListView() {
var listView = SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(_, index) => ListTile(title: Text("$index")),
childCount: 15));

return CustomScrollView(
slivers: [listView, listView],
);
}

其中 ​​SliverFixedExtentList​​​ 是一个 Sliver,它可以生成高度相同的列表项,如果列表项高度相同,应该优先使用 SLiverFixedExtentList 或者 ​​SliverPrototypeExtentList​​​,如果不同再使用 ​​SliverList​

Flutter学习 可滚动Widget 中_ide_06


这就达到我们想要的效果了。

10.2 Flutter 中常用的 Sliver

前面介绍了 ​​SLiverFixedExtentList​​ 是高度固定的列表,除此之外,还有其他的 Sliver , 如下图所示:

Flutter学习 可滚动Widget 中_ico_07


上面这是都是和列表对应的 Sliver, 还有一些是用于对 Sliver 进行布局、装饰的组件,例如:

Flutter学习 可滚动Widget 中_ico_08


Sliver 系列的组件会比较多,这里只需要去记住其特点即可。

10.2.1 示例

下面是官方的 Demo:

override
Widget build(BuildContext context) {
return Material(
child: CustomScrollView(
slivers: [
// App Bar, 是一个导航栏
SliverAppBar(
// 滑动到顶端时会固定住
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: const Text("Sliver Demo"),
background: Image.asset("images/bobo.jpg", fit: BoxFit.cover),
),
),
SliverPadding(
padding: const EdgeInsets.all(10.0),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// 两列显示
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 3.0),
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
alignment: Alignment.center,
// 渐变
color: Colors.cyan[100 * (index % 9)],
child: Text("grid item $index"),
);
}, childCount: 20),
),
),
SliverFixedExtentList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * index % 9],
child: Text("list item $index"),
);
}, childCount: 20),
itemExtent: 50.0),
],
));
}

效果为:

Flutter学习 可滚动Widget 中_ide_09


Flutter学习 可滚动Widget 中_ide_10

10.2.2 SliverToBoxAdapter

出现了:列表项必有的适配器!

在实际布局中,我们通常都要往 CustomScrollView 去添加自定义组件,但往往这些组件并非有 Sliver 版本,为此 Flutter 提供了一个适配器组件: ​​SliverToBoxAdapter​​, 可以将 RenderBox 适配为 Sliver,比如我们想要在列表顶部添加一个可以横向滑动的 PageView, 可以使用 ​​SliverToBoxAdapter​​:

CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: SizedBox(
height: 300.0,
child: PageView(
children: const [Text("1"), Text("2")],
),
),
),
buildSliverFixedList(),
],
),

PageView 没有 Sliver 版本,所以使用了上面的代码中添加了这个适配器。

但是要注意的是, 如果将 PageView 替换成一个滑动方向和父组件 CustomScrollView 的ListView,则不会正常工作。 原因是: CustomScrollView 为所有 子Sliver 提供一个共享的 Scrollable, 然后统一处理指定滑动方向的滑动事件, 如果 Sliver 中引入了其他 Scrollable,就会产生滑动事件冲突。 最终效果是 ListView 内滑动只会对 ListView 起作用, Flutter 中的手势冲突,默认是子元素生效

CustomScrollView 引入一个滑动方向一样的子组件,则不能正常工作,为了解决这个问题,可以换成 ​​NestedScrollView​

10.2.3 SliverPersistentHeader

​SliverPersistentHeader​​ 的功能是滑动到 CustomScrollView 顶部时,将组件固定在顶部

可是之前已经学到 ​​SliverAppBar​​​ 这个玩意了,其实 Flutter 设计这个组件的 初衷就是为了实现 ​​SliverAppBar​​, 所以他们的属性和回调在 SlvierAppBar 中才会用到。

我们来看看其定义:

const SliverPersistentHeader({
Key? key,
required this.delegate,
this.pinned = false,
this.floating = false,
})

  • ​delegate​​ 用于构造 header 组件委托
  • ​floating​​​​pinned​​ 为 false 时, 则 header 可以滑出可视区域 (Viewport),当用户再次向下滑动时,此时不管 header 已经被滑出了多远,它都会立即出现在可视区域顶部并被固定住, 直到继续下滑到 header 在列表中原有的位置时, header 才会重新回到原来的位置

大家可以看下官网得到 delegate 构建: ​​官网 SliverPersistentHeaderDelegate 封装实现​​实现如下效果:

Flutter学习 可滚动Widget 中_ide_11


举报

相关推荐

0 条评论