GridView

GridView可以构建一个二维网格列表,其默认构造函数定义如下:

  1. GridView({
  2. Axis scrollDirection = Axis.vertical,
  3. bool reverse = false,
  4. ScrollController controller,
  5. bool primary,
  6. ScrollPhysics physics,
  7. bool shrinkWrap = false,
  8. EdgeInsetsGeometry padding,
  9. @required SliverGridDelegate gridDelegate, //控制子widget layout的委托
  10. bool addAutomaticKeepAlives = true,
  11. bool addRepaintBoundaries = true,
  12. double cacheExtent,
  13. List<Widget> children = const <Widget>[],
  14. })

我们可以看到,GridView和ListView的大多数参数都是相同的,它们的含义也都相同,如有疑惑读者可以翻阅ListView一节,在此不再赘述。我们唯一需要关注的是gridDelegate参数,类型是SliverGridDelegate,它的作用是控制GridView子widget如何排列(layout),SliverGridDelegate是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法,Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,下面我们分别介绍:

SliverGridDelegateWithFixedCrossAxisCount

该子类实现了一个纵轴为固定数量子元素的layout算法,其构造函数为:

  1. SliverGridDelegateWithFixedCrossAxisCount({
  2. @required 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两个参数共同决定的。注意,这里的子元素指的是子widget的最大显示空间,注意确保子widget的实际大小不要超出子元素的空间。

下面看一个例子:

  1. GridView(
  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. );

image-20180912110415233

GridView.count

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

  1. GridView.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算法,其构造函数为:

  1. SliverGridDelegateWithMaxCrossAxisExtent({
  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]内的话,子元素最终实际长度都为150,而childAspectRatio所指的子元素纵轴和主轴的长度比为最终的长度比。其它参数和SliverGridDelegateWithFixedCrossAxisCount相同。

下面我们看一个例子:

  1. GridView(
  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. );

image-20180912114320838

GridView.extent

GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建纵轴子元素为固定最大长度的的GridView,上面的示例代码等价于:

  1. GridView.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 必须指定的参数有两个:

  1. GridView.builder(
  2. ...
  3. @required SliverGridDelegate gridDelegate,
  4. @required IndexedWidgetBuilder itemBuilder,
  5. )

其中itemBuilder为子widget构建器。

示例

假设我们需要从一个异步数据源(如网络)分批获取一些Icon,然后用GridView来展示:

  1. class InfiniteGridView extends StatefulWidget {
  2. @override
  3. _InfiniteGridViewState createState() => new _InfiniteGridViewState();
  4. }
  5. class _InfiniteGridViewState extends State<InfiniteGridView> {
  6. List<IconData> _icons = []; //保存Icon数据
  7. @override
  8. void initState() {
  9. // 初始化数据
  10. _retrieveIcons();
  11. }
  12. @override
  13. Widget build(BuildContext context) {
  14. return GridView.builder(
  15. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  16. crossAxisCount: 3, //每行三列
  17. childAspectRatio: 1.0 //显示区域宽高相等
  18. ),
  19. itemCount: _icons.length,
  20. itemBuilder: (context, index) {
  21. //如果显示到最后一个并且Icon总数小于200时继续获取数据
  22. if (index == _icons.length - 1 && _icons.length < 200) {
  23. _retrieveIcons();
  24. }
  25. return Icon(_icons[index]);
  26. }
  27. );
  28. }
  29. //模拟异步获取数据
  30. void _retrieveIcons() {
  31. Future.delayed(Duration(milliseconds: 200)).then((e) {
  32. setState(() {
  33. _icons.addAll([
  34. Icons.ac_unit,
  35. Icons.airport_shuttle,
  36. Icons.all_inclusive,
  37. Icons.beach_access, Icons.cake,
  38. Icons.free_breakfast
  39. ]);
  40. });
  41. });
  42. }
  43. }
  • _retrieveIcons():在此方法中我们通过Future.delayed来模拟从异步数据源获取数据,每次获取数据需要200毫秒,获取成功后将新数据添加到_icons,然后调用setState重新构建。
  • 在itemBuilder中,如果显示到最后一个时,判断是否需要继续获取数据,然后返回一个Icon。

更多

Flutter的GridView默认子元素显示空间是相等的,但在实际开发中,你可能会遇到子元素大小不等的情况,如下面这样的布局:

flutter_staggered_grid_view

Pub上有一个包“flutter_staggered_grid_view” ,它实现了一个交错GridView的布局模型,可以很轻松的实现这种布局,详情读者可以自行了解。