GridView with fixed height in Flutter
I wanted to convert a ListView
into a GridView
so that it would show more information on a large screen such as Web.
My ListView
code looked like this:
return ListView.builder(
physics: scrollable ? null : NeverScrollableScrollPhysics(),
shrinkWrap: shrinkWrap,
padding: EdgeInsets.only(top: 16.0),
itemCount: words.length,
itemBuilder: (context, index) =>
buildListItem(context, words[index]),
);
I found that GridView used almost the same parameters, and took on top of that a gridDelegate.
The doc says that for large or infinite grids, you should use the builder constructor. Good, that's what I'm looking for. Also for gridDelegates, they list two of them:
Looking at the description for MaxCrossAxisExtent
, which says it'll divide columns if the width of the item is larger than a threshold, this is perfect for me! And here I thought I'd need a LayoutBuilder and do width/column/spacing maths.
So I tried introduced the GridView.builder
with the delegate, and ran it. It automatically created the columns as expected. However, the height of the grid items was all wrong. I thought it had to do with how my items had some Columns in them. I tried to set the Columns' mainAxisSize
to min. It did nothing.
After googling it, I realized that it was because by default, the childAspectRatio
is 1, so it will make square tiles. See two results:
- https://stackoverflow.com/questions/48405123/how-to-set-custom-height-for-widget-in-gridview-in-flutter
- https://github.com/flutter/flutter/issues/55290
Judging by these posts, it seems like Flutter doesn't provide a way to set a height besides computing an aspect ratio yourself. Some users provide some fancy maths, while one other person suggested to copy/paste reimplement their own version of a delegate like SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight
.
Out of curiosity, instead of reimplementing the whole class, I wanted to see if it was possible to extend the desired class, and override the getLayout
method to use the same code, except for the height value.
As I browsed through the code, I found this (source code link):
final double childMainAxisExtent = mainAxisExtent ?? childCrossAxisExtent / childAspectRatio;
Wait, so there's a way to not use childAspectRatio? What is this mainAxisExtent? It turns out, it's an optional parameter, and it does exactly what we wanted (source code link):
/// The extent of each tile in the main axis. If provided it would define the
/// logical pixels taken by each tile in the main-axis.
///
/// If null, [childAspectRatio] is used instead.
final double? mainAxisExtent;
With hindsight, the parameter was in the documentation all along. https://api.flutter.dev/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent/mainAxisExtent.html
TLDR
Here's the final piece of code:
return GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 500,
mainAxisExtent: 77,
),
physics: scrollable ? null : NeverScrollableScrollPhysics(),
shrinkWrap: shrinkWrap,
padding: EdgeInsets.only(top: 16.0),
itemCount: words.length,
itemBuilder: (context, index) =>
buildListItem(context, words[index]),
);