6.6 GridView
GridView可以构建一个二维网格列表,其默认构造函数定义如下:
1 GridView({
2 Key? key,
3 Axis scrollDirection = Axis.vertical,
4 bool reverse = false,
5 ScrollController? controller,
6 bool? primary,
7 ScrollPhysics? physics,
8 bool shrinkWrap = false,
9 EdgeInsetsGeometry? padding,
10 required this.gridDelegate, //下面解释
11 bool addAutomaticKeepAlives = true,
12 bool addRepaintBoundaries = true,
13 double? cacheExtent,
14 List<Widget> children = const <Widget>[],
15 ...
16 })我们可以看到,GridView和ListView的大多数参数都是相同的,它们的含义也都相同的,如有疑惑读者可以翻阅ListView一节,在此不再赘述。我们唯一需要关注的是gridDelegate参数,类型是SliverGridDelegate,它的作用是控制GridView子组件如何排列(layout)。
SliverGridDelegate是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法。Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,我们可以直接使用,下面我们分别来介绍一下它们。
SliverGridDelegateWithFixedCrossAxisCount
该子类实现了一个横轴为固定数量子元素的layout算法,其构造函数为:
1SliverGridDelegateWithFixedCrossAxisCount({
2 double crossAxisCount,
3 double mainAxisSpacing = 0.0,
4 double crossAxisSpacing = 0.0,
5 double childAspectRatio = 1.0,
6})crossAxisCount:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商。mainAxisSpacing:主轴方向的间距。crossAxisSpacing:横轴方向子元素的间距。childAspectRatio:子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度。
可以发现,子元素的大小是通过crossAxisCount和childAspectRatio两个参数共同决定的。注意,这里的子元素指的是子组件的最大显示空间,注意确保子组件的实际大小不要超出子元素的空间。
下面看一个例子:
1GridView(
2 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
3 crossAxisCount: 3, //横轴三个子widget
4 childAspectRatio: 1.0 //宽高比为1时,子widget
5 ),
6 children:<Widget>[
7 Icon(Icons.ac_unit),
8 Icon(Icons.airport_shuttle),
9 Icon(Icons.all_inclusive),
10 Icon(Icons.beach_access),
11 Icon(Icons.cake),
12 Icon(Icons.free_breakfast)
13 ]
14);
GridView.count
GridView.count构造函数内部使用了SliverGridDelegateWithFixedCrossAxisCount,我们通过它可以快速的创建横轴固定数量子元素的GridView,上面的示例代码等价于:
1GridView.count(
2 crossAxisCount: 3,
3 childAspectRatio: 1.0,
4 children: <Widget>[
5 Icon(Icons.ac_unit),
6 Icon(Icons.airport_shuttle),
7 Icon(Icons.all_inclusive),
8 Icon(Icons.beach_access),
9 Icon(Icons.cake),
10 Icon(Icons.free_breakfast),
11 ],
12);SliverGridDelegateWithMaxCrossAxisExtent
该子类实现了一个横轴子元素为固定最大长度的layout算法,其构造函数为:
1SliverGridDelegateWithMaxCrossAxisExtent({
2 double maxCrossAxisExtent,
3 double mainAxisSpacing = 0.0,
4 double crossAxisSpacing = 0.0,
5 double childAspectRatio = 1.0,
6})maxCrossAxisExtent为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的,举个例子,如果ViewPort的横轴长度是450,那么当maxCrossAxisExtent的值在区间[450/4,450/3)内的话,子元素最终实际长度都为112.5,而childAspectRatio所指的子元素横轴和主轴的长度比为最终的长度比。其它参数和SliverGridDelegateWithFixedCrossAxisCount相同。
下面我们看一个例子:
1GridView(
2 padding: EdgeInsets.zero,
3 gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
4 maxCrossAxisExtent: 120.0,
5 childAspectRatio: 2.0 //宽高比为2
6 ),
7 children: <Widget>[
8 Icon(Icons.ac_unit),
9 Icon(Icons.airport_shuttle),
10 Icon(Icons.all_inclusive),
11 Icon(Icons.beach_access),
12 Icon(Icons.cake),
13 Icon(Icons.free_breakfast),
14 ],
15);
GridView.extent
GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建纵轴子元素为固定最大长度的的GridView,上面的示例代码等价于:
1GridView.extent(
2 maxCrossAxisExtent: 120.0,
3 childAspectRatio: 2.0,
4 children: <Widget>[
5 Icon(Icons.ac_unit),
6 Icon(Icons.airport_shuttle),
7 Icon(Icons.all_inclusive),
8 Icon(Icons.beach_access),
9 Icon(Icons.cake),
10 Icon(Icons.free_breakfast),
11 ],
12 );GridView.builder
上面我们介绍的GridView都需要一个widget数组作为其子元素,这些方式都会提前将所有子widget都构建好,所以只适用于子widget数量比较少时,当子widget比较多时,我们可以通过GridView.builder来动态创建子widget。GridView.builder 必须指定的参数有两个:
1GridView.builder(
2 ...
3 required SliverGridDelegate gridDelegate,
4 required IndexedWidgetBuilder itemBuilder,
5)其中itemBuilder为子widget构建器。
示例
假设我们需要从一个异步数据源(如网络)分批获取一些Icon,然后用GridView来展示:
1class InfiniteGridView extends StatefulWidget {
2
3 _InfiniteGridViewState createState() => _InfiniteGridViewState();
4}
5
6class _InfiniteGridViewState extends State<InfiniteGridView> {
7 List<IconData> _icons = []; //保存Icon数据
8
9
10 void initState() {
11 super.initState();
12 // 初始化数据
13 _retrieveIcons();
14 }
15
16
17 Widget build(BuildContext context) {
18 return GridView.builder(
19 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
20 crossAxisCount: 3, //每行三列
21 childAspectRatio: 1.0, //显示区域宽高相等
22 ),
23 itemCount: _icons.length,
24 itemBuilder: (context, index) {
25 //如果显示到最后一个并且Icon总数小于200时继续获取数据
26 if (index == _icons.length - 1 && _icons.length < 200) {
27 _retrieveIcons();
28 }
29 return Icon(_icons[index]);
30 },
31 );
32 }
33
34 //模拟异步获取数据
35 void _retrieveIcons() {
36 Future.delayed(Duration(milliseconds: 200)).then((e) {
37 setState(() {
38 _icons.addAll([
39 Icons.ac_unit,
40 Icons.airport_shuttle,
41 Icons.all_inclusive,
42 Icons.beach_access,
43 Icons.cake,
44 Icons.free_breakfast,
45 ]);
46 });
47 });
48 }
49}_retrieveIcons():在此方法中我们通过Future.delayed来模拟从异步数据源获取数据,每次获取数据需要200毫秒,获取成功后将新数据添加到_icons,然后调用setState重新构建。- 在 itemBuilder 中,如果显示到最后一个时,判断是否需要继续获取数据,然后返回一个Icon。