SliverAppBar\SliverAppBar\SliverGrid\SliverList

人间烟火/张佳伟版 Lv2

什么是SliverAppBar

在Flutter中,SliverAppBar是AppBar部件的继承者,它允许您创建浮动的应用栏效果。SliverAppBar在屏幕向上滚动时展开AppBar,向下滚动时折叠。

当用户向下滚动一个长的列表时,你也可以完全删除或隐藏AppBar。SliverAppBar有很多自定义选项,所以你可以根据你的需要来定制它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
return Scaffold(
//1
body: CustomScrollView(
slivers: <Widget>[
//2
SliverAppBar(
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Goa', textScaleFactor: 1),
background: Image.asset(
'assets/images/beach.png',
fit: BoxFit.fill,
),
),
),
//3
SliverList(
delegate: SliverChildBuilderDelegate(
(_, int index) {
return ListTile(
leading: Container(
padding: EdgeInsets.all(8),
width: 100,
child: Placeholder()),
title: Text('Place ${index + 1}', textScaleFactor: 2),
);
},
childCount: 20,
),
),
],
),
);

===
要添加CustomScrollView,请将CustomScrollView ,放在Scaffold widget的body部分。这是用来同步AppBar和列表的滚动位置的。

有几个小部件可以被添加到CustomScrollView中,SliverAppBar就是其中之一。

SliverAppBar提供了普通AppBar小部件的所有功能,并增加了动画功能。
flexibleSpace ,用于在AppBar展开时显示任何小部件。
expandedHeight ,用于设置FlexibleSpaceBar这个小部件的高度。


SliverList显示项目的列表。我们不能使用普通的ListView类,因为CustomScrollView接受sliver类型的widget。

SliverAppBar有三个重要的属性,即pinned,snap 和floating 。设置这三个参数的组合可以使SliverAppBar按照你的需要工作。

1.只设置一个钉子的值为true ,当向下滚动时,SliverAppBar就会粘在顶部。当向上滚动时,SliverAppBar只在到达列表中的第一个项目时展开。

2.当所有的参数都设置为true ,SliverAppBar在向下滚动时停留在顶部,向上滚动时完全展开,即使没有到达列表中的第一个项目。(这样有可能下面的列表会被flexbleSpace遮挡)

3.当只有snap值被设置为false ,SliverAppBar在向下滚动时保持在顶部。当我们向上滚动时,背景图片开始膨胀,并随着我们的滚动而停止。

4.只将浮动值设置为true ,在向下滚动时隐藏SliverAppBar,当我们向上滚动时开始显示背景图片。

5.如果你想让SliverAppBar在向下滚动时保持隐藏,而在向上滚动时显示完整的背景图片,即使是在列表中的第一个项目没有显示时。你可以只设置snap和floating为true 。

在SliverAppBar内添加AppBar

需要注意的是,SliverAppBar并不能完全替代正常的AppBar。在Flutter中编写应用程序的好处是,你可以混合和匹配小部件来创造新的东西。

例如,你可能会遇到这样的情况:你需要在SliverAppBar内显示一个包含搜索框的AppBar。

假如:当向下滚动时,横幅图片被隐藏,而搜索框仍然停留在顶部。

首先,只要在SliverAppBar的bottom 属性中写一个普通的AppBar。该AppBar将包含TextField部件作为搜索项目的输入框。
项目的列表显示在SliverGrid中。由于我们使用了CutomScrollView,我们不能在这里使用普通的GridView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
...
bottom: AppBar(
title: Container(
height: 45,
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter a search term'),
),
),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 2,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ItemTile(index);
},
),
),
],
),
)

用SliverAppBar添加TabBar

TabBar小部件用于显示不同类别的内容或用户可用的功能。在某些情况下,你可能想用SliverAppBar来显示TabBar。

NestedScrollView部件被用来返回标题,作为SliverAppBar和SliverPersistentHeader部件的组合。SliverAppBar内部使用SliverPersistentHeader来实现收缩和增长的效果。你可以使用这个小部件来显示SliverAppBar下面的标签。

TabBarView在NestedScrollView widget的body 参数中给出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Scaffold(
body: DefaultTabController(
length: 3,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
pinned: false,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Goa', textScaleFactor: 1),
background: Image.asset(
'assets/images/beach.png',
fit: BoxFit.fill,
),
stretchModes: [StretchMode.zoomBackground],
),
//collapsedHeight: 100,
),
SliverPersistentHeader(
delegate: MySliverPersistentHeaderDelegate(
TabBar(
tabs: [
Tab(icon: Icon(Icons.flight)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_car)),
],
),
),
pinned: false,
),
];
},
body: TabBarView(
children: [
Icon(Icons.flight, size: 350),
Icon(Icons.directions_transit, size: 350),
Icon(Icons.directions_car, size: 350),
],
),
),
),
)

为了改善用户体验,在向下滚动时,你应该始终保持TabBar在顶部可见。
在SliverPersistentHeader中设置pinned 值为true ,将解决这个问题。


监听SliverAppBar的状态(展开或折叠

如果想要监听SliverAppBar的状态,以确定它是展开的还是折叠的,你可以使用返回的值来改变SliverAppBar的设置。例如,当它被展开时,你可以改变标题的文本颜色。

ScrollController被创建并分配给CustomScrollView
监听器被添加到ScrollController中,以计算SliverAppBar是否被展开。
从监听器返回的值被用来设置标题的文本颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
late ScrollController _scrollController;
//----------
@override
void initState() {
// TODO: implement initState
super.initState();

_scrollController = ScrollController()
..addListener(() {
setState(() {
_textColor = _isSliverAppBarExpanded ? Colors.white : Colors.blue;
});
});
}
//----------
bool get _isSliverAppBarExpanded {
return _scrollController.hasClients &&
_scrollController.offset > (200 - kToolbarHeight);
}
//----------
Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: ...,
),
);

除了SliverAppBar之外,我们还可以为CustomScrollView添加List或者Grid来实现更加复杂的组合效果

SliverList继承自SliverMultiBoxAdaptorWidget,它的构造函数比较简单,需要传入一个SliverChildDelegate的参数,这里的SliverChildDelegate使用的是delegate的方法来创建SliverList的子组件。

SliverChildDelegate是一个抽象类,它有两个实现类,分别是SliverChildBuilderDelegate和SliverChildListDelegate。

其中SliverChildBuilderDelegate是用的builder模式来生成子widget,在上一篇文章中,我们构建SliverList就是使用的这个builder类。

SliverChildBuilderDelegate使用builder来生成子Widget,而SliverChildListDelegate需要传入一个childList来完成构造,也就是说SliverChildListDelegate需要一个确切的childList,而不是用builder来构建。

要注意的是SliverList并不能指定子widget的extent大小,如果你想指定List中的子widget的extent大小的话,那么可以使用SliverFixedExtentList:

1
2
3
4
5
6
class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget {
const SliverFixedExtentList({
Key? key,
required SliverChildDelegate delegate,
required this.itemExtent,
}) : super(key: key, delegate: delegate);

可以看到SliverFixedExtentList和SliverList相比,多了一个itemExtent参数,用来控制子widget在main axis上的大小


SliverGrid也是继承自SliverMultiBoxAdaptorWidget,和SliverList一样,它也有一个SliverChildDelegate的参数,另外它还多了一个gridDelegate的参数用来控制gird的布局。

这里的gridDelegate是一个SliverGridDelegate类型的参数,用来控制children的size和position。

SliverGridDelegate也是一个抽象类,它有两个实现类,分别是SliverGridDelegateWithMaxCrossAxisExtent和SliverGridDelegateWithFixedCrossAxisCount,这两个实现类的区别就在于MaxCrossAxisExtent和FixedCrossAxisCount的区别。

怎么理解MaxCrossAxisExtent呢?比如说这个Grid是竖向的,然后Gird的宽度是500.0,如果MaxCrossAxisExtent=100,那么delegate将会创建5个column,每个column的宽度是100。

crossAxisCount则是直接指定cross axis的child个数有多少。

SliverList和SliverGird的使用

有了上面介绍的SliverList和SliverGird的构造函数,接下来我们具体来看下如何在项目中使用SliverList和SliverGird。

默认情况下SliverList和SliverGird是需要和CustomScrollView一起使用的,所以我们先创建一个CustomScrollView,在它的slivers属性中,放入一个SliverAppBar组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
snap: false,
floating: false,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('SliverList and SliverGrid'),
),
),
],
);
SliverAppBar只是一个AppBar,我们还需要为它继续添加其他的slivers组件,首先给他添加一个SliverGrid:

我们设置了gridDelegate属性,并且自定义了SliverChildBuilderDelegate,用来生成20个Container。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 20.0,
crossAxisSpacing: 50.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.green[100 * (index % 9)],
child: Text('grid item $index'),
);
},
childCount: 20,
),
),

因为SliverList只需要传入一个delegate参数,这里我们生成了15个child组件
然后为其添加SliverList:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: index.isOdd ? Colors.white : Colors.green,
height: 50.0,
child: Center(
child: ListTile(
title: Text(
'100' + index.toString(),
style: const TextStyle(fontWeight: FontWeight.w500),
),
leading: Icon(
Icons.account_box,
color: Colors.green[100 * (index % 9)],
),
),
),
);
},
childCount: 15,
),
),

因为SliverList不能控制List中子widget的extent,所以我们再添加一个SliverFixedExtentList看看效果:
SliverFixedExtentList和SliverList相比多了一个itemExtent属性,这里我们将其设置为100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SliverFixedExtentList(
itemExtent: 100.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: index.isOdd ? Colors.white : Colors.green,
height: 50.0,
child: Center(
child: ListTile(
title: Text(
'200' + index.toString(),
style: const TextStyle(fontWeight: FontWeight.w500),
),
leading: Icon(
Icons.account_box,
color: Colors.green[100 * (index % 9)],
),
),
),
);
},
childCount: 15,
),
  • Title: SliverAppBar\SliverAppBar\SliverGrid\SliverList
  • Author: 人间烟火/张佳伟版
  • Created at: 2024-04-24 22:43:23
  • Updated at: 2024-04-25 22:44:17
  • Link: https://945912035.github.io/2024/04/24/2024-4-24/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments
On this page
SliverAppBar\SliverAppBar\SliverGrid\SliverList