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 bycrossAxisCount
.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 bycrossAxisCount
, 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 .
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 .
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.