0%

常见的Widget使用

ScrollView in Flutter

想象有一种应用场景,scrollable组件内部嵌套另一个scrollable组件。特别是当同时显示ListView和GridView时应该怎么处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
return Scaffold (
appBar: AppBar(
title: Text('listView')
),
body: Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
someWidget,
someWidget,
someWidget,
ListView(
children: <Widget>[
anotherWidget,
anotherWidget,
anotherWidget
]
)
]
)
)
)
)

运行以上代码,控制台会抛出异常,其中关键的一句翻译过来就是:

垂直视口被赋予无限高度。这种情况通常在可滚动小部件嵌套在另一个可滚动小部件内时发生。

这个时候,我们可以通过Slivers来实现这一需求。通过以下代码替换:

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 (
appBar: AppBar(
title: Text('listView')
),
body: Container(
child: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(
someWidget,
someWidget,
someWidget,
)
),
SliverList(
delegate: SliverChildListDelegate(
anotherWidget,
anotherWidget,
anotherWidget,
)
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
delegate: SliverChildListDelegate(
thirdWidget,
thirdWidget,
thirdWidget,
)
),
]
)
)
)


下面介绍Slivers系列常见的控件及使用场景

SliverAppBar

经常用于AppBar展开收起的场景,通过配置flexibleSpace和expandedHeight属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
actions: <Widget>[
_buildAction(),
],
title: Text('SliverAppBar'),
backgroundColor: Theme.of(context).accentColor,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
background: Image.asset('images/food01.jpeg', fit: BoxFit.cover),
),
),
SliverFixedExtentList(
itemExtent: 120.0,
delegate: SliverChildListDelegate(
products.map((product) {
return _buildItem(product);
}).toList(),
),
),
],
);


flexibleSpace是被展开和收起的组件,expandedHeight是其操控的高度;其他属性具体含义可以参考官方文档

SliverList

SliverList只需要设置delegate属性就可以,可以滑动的列表,常常用于滑动组件嵌套的场景

1
2
3
4
5
6
7
8
9
10
11
12
CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _buildItem(context, products[index]);
},
childCount: 3,
),
)
],
);

也可以通过SliverChildListDelegate来构建

1
2
3
4
5
6
7
8
9
10
11
CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
_buildItem(),
_buildItem(),
_buildItem(),
]),
)
],
);
SliverChildListDelegate和SliverChildBuilderDelegate的区别:

SliverChildListDelegate一般用来构item建数量明确的列表,会提前build好所有的子item,所以在效率上会有问题,适合item数量不多的情况。
SliverChildBuilderDelegate构建的列表理论上是可以无限长的。
两者的区别有些类似于ListView和ListView.builder()的区别。



SliverGrid

SliverGrid有三个构造函数:SliverGrid.count()、SliverGrid.extent和SliverGrid()。

  • SliverGrid.count()指定了一行展示多少个item,下面的例子表示一行展示4个:
1
SliverGrid.count(children: scrollItems, crossAxisCount: 4)
  • SliverGrid.extent可以指定item的最大宽度,然后让Flutter自己决定一行展示多少个item
1
SliverGrid.extent(children: scrollItems, maxCrossAxisExtent: 90.0)
  • SliverGrid()则是需要指定一个gridDelegate,它提供给了程序员一个自定义Delegate的入口,你可以自己决定每一个item怎么排列
1
2
3
4
5
6
7
8
9
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _buildItem(products[index]);;
}
);


SliverPersistentHeader

SliverPersistentHeader顾名思义,就是给一个可滑动的视图添加一个头(实际上,在CustomScrollView的slivers列表中,header可以出现在视图的任意位置,不一定要是在顶部)。这个Header会随着滑动而展开/收起

1
2
3
4
5
6
7
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
minHeight: 60.0,
maxHeight: 180.0,
child: Container(),
),
);

构建一个SliverPersistentHeader需要传入一个delegate,这个delegate是SliverPersistentHeaderDelegate类型的,而SliverPersistentHeaderDelegate是一个abstract类,我们不能直接new一个SliverPersistentHeaderDelegate出来,因此,我们需要自定义一个delegate来实现SliverPersistentHeaderDelegate类:

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
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});

final double minHeight;
final double maxHeight;
final Widget child;

@override
double get minExtent => minHeight;

@override
double get maxExtent => math.max(maxHeight, minHeight);

@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}

@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}

写一个自定义SliverPersistentHeaderDelegate很简单,只需重写build()、get maxExtent、get minExtent和shouldRebuild()这四个方法,上面就是一个最简单的SliverPersistentHeaderDelegate的实现。其中,maxExtent表示header完全展开时的高度,minExtent表示header在收起时的最小高度。因此,对于我们上面的那个自定义Delegate,如果将minHeight和maxHeight的值设置为相同时,header就不会收缩了,这样的Header跟我们平常理解的Header更像。



SliverToBoxAdapter

 SliverPersistentHeader一般来说都是会展开/收起的(除非minExtent和maxExtent值相同),那么如果想要在滚动视图中添加一个普通的控件,那么就可以使用SliverToBoxAdapter来将各种视图组合在一起,放在CustomListView中。

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
CustomScrollView(
physics: ScrollPhysics(),
slivers: <Widget>[
SliverToBoxAdapter(
child: _buildHeader(),
),
SliverGrid.count(
crossAxisCount: 3,
children: products.map((product) {
return _buildItemGrid(product);
}).toList(),
),
SliverToBoxAdapter(
child: _buildSearch(),
),
SliverFixedExtentList(
itemExtent: 100.0,
delegate: SliverChildListDelegate(
products.map((product) {
return _buildItemList(product);
}).toList(),
),
),
SliverToBoxAdapter(
child: _buildFooter(),
),
],
);