Flutter (42): GridView

Time: Column:Mobile & Frontend views:274

A grid layout is a common layout type, and the GridView widget is used to implement this layout in Flutter. This section focuses on its usage.

42.1 Default Constructor

GridView can build a two-dimensional grid list, and its default constructor is defined as follows:

GridView({
  Key? key,
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController? controller,
  bool? primary,
  ScrollPhysics? physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry? padding,
  required this.gridDelegate,  // Explained below
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double? cacheExtent, 
  List<Widget> children = const <Widget>[],
  ...
})

As you can see, most of the parameters in GridView are the same as those in ListView, and their meanings are identical. If you have any questions, you can refer to the ListView section. The only parameter we need to focus on is gridDelegate, which is of type SliverGridDelegate and controls how the GridView arranges its child widgets.

SliverGridDelegate is an abstract class that defines the layout interface for GridView. Its subclasses must implement the necessary methods to define a specific layout algorithm. Flutter provides two built-in subclasses of SliverGridDelegate: SliverGridDelegateWithFixedCrossAxisCount and SliverGridDelegateWithMaxCrossAxisExtent, which we can use directly. Let's explore each of them.

1. SliverGridDelegateWithFixedCrossAxisCount

This subclass implements a layout algorithm where the number of items on the cross-axis is fixed. Its constructor is defined as follows:

SliverGridDelegateWithFixedCrossAxisCount({
  @required double crossAxisCount, 
  double mainAxisSpacing = 0.0,
  double crossAxisSpacing = 0.0,
  double childAspectRatio = 1.0,
})
  • crossAxisCount: The number of items along the cross-axis. Once this value is set, the item width on the cross-axis is determined, i.e., the length of the viewport's cross-axis divided by crossAxisCount.

  • mainAxisSpacing: The spacing along the main axis.

  • crossAxisSpacing: The spacing between items along the cross-axis.

  • childAspectRatio: The ratio of the item's width to height. Since the width of the item on the cross-axis is determined by crossAxisCount, this value determines the item's height.

The size of the grid items is determined by both crossAxisCount and childAspectRatio. Note that the "item" here refers to the maximum available display space for each child widget, so ensure that the actual size of the child widgets does not exceed this space.

Here’s an example:

GridView(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 3, // Three widgets per row
      childAspectRatio: 1.0 // Width-to-height ratio is 1
  ),
  children: <Widget>[
    Icon(Icons.ac_unit),
    Icon(Icons.airport_shuttle),
    Icon(Icons.all_inclusive),
    Icon(Icons.beach_access),
    Icon(Icons.cake),
    Icon(Icons.free_breakfast)
  ]
);

The result is shown in Figure .

Flutter (42): GridView

2. SliverGridDelegateWithMaxCrossAxisExtent

This subclass implements a layout algorithm where each item has a maximum width along the cross-axis. Its constructor is defined as follows:

SliverGridDelegateWithMaxCrossAxisExtent({
  double maxCrossAxisExtent,
  double mainAxisSpacing = 0.0,
  double crossAxisSpacing = 0.0,
  double childAspectRatio = 1.0,
})

maxCrossAxisExtent sets the maximum width for items along the cross-axis. The width of each item is still equally divided along the cross-axis. For example, if the viewport's cross-axis length is 450, and the value of maxCrossAxisExtent falls within the range [450/4, 450/3), then each item's final width will be 112.5. The childAspectRatio defines the ratio of the item's width to height, determining its final size. Other parameters are the same as in SliverGridDelegateWithFixedCrossAxisCount.

Here’s an example:

GridView(
  padding: EdgeInsets.zero,
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
      maxCrossAxisExtent: 120.0,
      childAspectRatio: 2.0 // Width-to-height ratio is 2
  ),
  children: <Widget>[
    Icon(Icons.ac_unit),
    Icon(Icons.airport_shuttle),
    Icon(Icons.all_inclusive),
    Icon(Icons.beach_access),
    Icon(Icons.cake),
    Icon(Icons.free_breakfast),
  ],
);

The result is shown in Figure .

Flutter (42): GridView

42.2 GridView.count

The GridView.count constructor internally uses SliverGridDelegateWithFixedCrossAxisCount. This allows you to quickly create a GridView with a fixed number of items along the cross-axis. The following code creates the same layout as the previous example:

GridView.count( 
  crossAxisCount: 3,
  childAspectRatio: 1.0,
  children: <Widget>[
    Icon(Icons.ac_unit),
    Icon(Icons.airport_shuttle),
    Icon(Icons.all_inclusive),
    Icon(Icons.beach_access),
    Icon(Icons.cake),
    Icon(Icons.free_breakfast),
  ],
);

42.3 GridView.extent

The GridView.extent constructor internally uses SliverGridDelegateWithMaxCrossAxisExtent. You can quickly create a GridView where items along the cross-axis have a maximum width, as shown in the following code:

GridView.extent(
   maxCrossAxisExtent: 120.0,
   childAspectRatio: 2.0,
   children: <Widget>[
     Icon(Icons.ac_unit),
     Icon(Icons.airport_shuttle),
     Icon(Icons.all_inclusive),
     Icon(Icons.beach_access),
     Icon(Icons.cake),
     Icon(Icons.free_breakfast),
   ],
 );

42.4 GridView.builder

In the previous examples, GridView required a list of child widgets to be provided. These methods are only suitable for small sets of child widgets because all widgets are built in advance. When you have a large number of widgets, you can use GridView.builder to dynamically create them. The required parameters for GridView.builder are:

GridView.builder(
 ...
 required SliverGridDelegate gridDelegate, 
 required IndexedWidgetBuilder itemBuilder,
)

The itemBuilder parameter is a builder function that constructs the child widgets.

Example

Let’s assume we need to retrieve a batch of icons from an asynchronous data source (like the network) and display them in a GridView:

class InfiniteGridView extends StatefulWidget {
  @override
  _InfiniteGridViewState createState() => _InfiniteGridViewState();
}

class _InfiniteGridViewState extends State<InfiniteGridView> {
  List<IconData> _icons = []; // Store icon data

  @override
  void initState() {
    super.initState();
    // Initialize data
    _retrieveIcons();
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3, // Three columns per row
        childAspectRatio: 1.0, // Square items
      ),
      itemCount: _icons.length,
      itemBuilder: (context, index) {
        // Fetch more data if the last icon is displayed and the total number of icons is less than 200
        if (index == _icons.length - 1 && _icons.length < 200) {
          _retrieveIcons();
        }
        return Icon(_icons[index]);
      },
    );
  }

  // Simulate asynchronous data retrieval
  void _retrieveIcons() {
    Future.delayed(Duration(milliseconds: 200)).then((e) {
      setState(() {
        _icons.addAll([
          Icons.ac_unit,
          Icons.airport_shuttle,
          Icons.all_inclusive,
          Icons.beach_access,
          Icons.cake,
          Icons.free_breakfast,
        ]);
      });
    });
  }
}

In the _retrieveIcons() method, we use Future.delayed to simulate asynchronous data retrieval, where each data fetch takes 200 milliseconds. Once the new data is fetched, it's added to _icons, and setState is called to rebuild the widget. In the itemBuilder, if the last item is reached, it checks whether more data needs to be fetched, and then an icon is returned.