https://api.flutter.dev/flutter/widgets/GridView-class.html

当数据量很大的时候用矩阵方式排列比较清晰。此时我们可以用网格列表组件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. })

gridDelegate 类型是 SliverGridDelegate ,作用是控制子控件的排列。

SliverGridDelegate是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法。有2个选择:

  • SliverGridDelegateWithFixedCrossAxisCount:交叉轴方向上固定数量,对于垂直方向的GridView来说交叉轴方向指的是水平方向。
  • SliverGridDelegateWithMaxCrossAxisExtent:交叉轴方向上尽量大,比如水平方上有500空间,指定此值为150,那么可以放3个,剩余一些空间,此时GridView将会缩小每一个Item,放置4个。


SliverGridDelegateWithFixedCrossAxisCount

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

  1. SliverGridDelegateWithFixedCrossAxisCount({
  2. @required double crossAxisCount,
  3. double mainAxisSpacing = 0.0,
  4. double crossAxisSpacing = 0.0,
  5. double childAspectRatio = 1.0,
  6. })
  • mainAxisSpacing 主轴方向的间距。
  • crossAxisSpacing 交叉轴方向上之间的间隔。
  • crossAxisCount 交叉轴方向上子元素的数量。
  • childAspectRatio 子控件宽高比。

示例

image.png

  1. GridView(
  2. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  3. crossAxisCount: 3,
  4. crossAxisSpacing: 5.0,
  5. mainAxisSpacing: 10.0,
  6. childAspectRatio: 16 / 5,
  7. ),
  8. children: List.generate(
  9. 10,
  10. (index) => Container(
  11. color: Colors.primaries[index % Colors.primaries.length],
  12. child: Text('数据 $index'),
  13. ),
  14. ),
  15. ),

GridView.count

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

  1. GridView.count(
  2. crossAxisCount: 3,
  3. crossAxisSpacing: 5.0,
  4. mainAxisSpacing: 10.0,
  5. childAspectRatio: 16 / 5,
  6. // children: ...,
  7. ),

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的横轴长度是480,那么当maxCrossAxisExtent的值在区间[480/4,480/3)内的话,子元素最终实际长度都为 480 / 4。
image.png

  1. GridView(
  2. gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
  3. maxCrossAxisExtent: 150,
  4. crossAxisSpacing: 5.0,
  5. mainAxisSpacing: 10.0,
  6. childAspectRatio: 16 / 5,
  7. ),
  8. children: List.generate(
  9. 10,
  10. (index) => Container(
  11. color: Colors.primaries[index % Colors.primaries.length],
  12. child: Text('数据 $index'),
  13. ),
  14. ),
  15. ),

GridView.extent

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

  1. GridView.extent(
  2. maxCrossAxisExtent: 150,
  3. crossAxisSpacing: 5.0,
  4. mainAxisSpacing: 10.0,
  5. childAspectRatio: 16 / 5,
  6. // children: ...,
  7. );

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. itemCount,
  6. )

其中itemBuilder为子widget构建器。

示例:

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

  1. import "package:flutter/material.dart";
  2. import "package:english_words/english_words.dart";
  3. class TestPage extends StatefulWidget {
  4. @override
  5. _TestPageState createState() => _TestPageState();
  6. }
  7. class _TestPageState extends State<TestPage> {
  8. var _myData = [];
  9. @override
  10. void initState() {
  11. super.initState();
  12. _getData();
  13. }
  14. //模拟异步获取数据
  15. _getData() {
  16. Future.delayed(Duration(seconds: 2)).then((val) {
  17. setState(() {
  18. var generateWords = generateWordPairs().take(10);
  19. var words = generateWords.map((v) => v.asPascalCase).toList();
  20. _myData.addAll(words);
  21. });
  22. });
  23. }
  24. @override
  25. Widget build(BuildContext context) {
  26. return GridView.builder(
  27. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  28. crossAxisCount: 3,
  29. childAspectRatio: 4,
  30. ),
  31. itemCount: _myData.length,
  32. itemBuilder: (context, index) {
  33. return Container(
  34. color: Colors.primaries[index % Colors.primaries.length],
  35. child: Text('单词 ${_myData[index]}'),
  36. );
  37. },
  38. );
  39. }
  40. }

更多

Flutter的GridView默认子元素显示空间是相等的,但在实际开发中,你可能会遇到子元素大小不等的情况,如下面这样的布局:
GridView - 图4
Pub上有一个包“flutter_staggered_grid_view” ,它实现了一个交错GridView的布局模型,可以很轻松的实现这种布局。