Dart Tutorial

Flutter Lesson 20 of 83 3 min read

GridView and Wrap in Flutter

Learn GridView in Flutter: fixed column counts, responsive grids with max extent, GridView.builder, aspect ratios, and Wrap for chips.

On this page

GridView arranges items in rows and columns and scrolls. Photo galleries, product catalogues and dashboards use it.

A fixed number of columns #

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Gallery')),
        body: GridView.builder(
          padding: const EdgeInsets.all(12),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            mainAxisSpacing: 12,
            crossAxisSpacing: 12,
          ),
          itemCount: 30,
          itemBuilder: (context, index) {
            return Container(
              decoration: BoxDecoration(
                color: Colors.primaries[index % Colors.primaries.length].shade200,
                borderRadius: BorderRadius.circular(12),
              ),
              alignment: Alignment.center,
              child: Text('$index'),
            );
          },
        ),
      ),
    ),
  );
}

Like ListView.builder, this builds only the visible cells.

Delegate propertyMeaning
crossAxisCountNumber of columns
mainAxisSpacingGap between rows
crossAxisSpacingGap between columns
childAspectRatioWidth divided by height of each cell. Default 1, a square

The size of a cell #

A grid gives every cell the same size and ignores the size its child asks for. You control the shape with childAspectRatio.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  childAspectRatio: 3 / 4, // taller than wide: good for product cards
)

If the content of your cells overflows, the cells are too short. Lower the ratio, or set an exact height with mainAxisExtent: 220.

A grid that adapts to the screen #

Fixed column counts look wrong on tablets. Give a maximum cell width and let Flutter work out how many columns fit.

GridView.builder(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200, // no cell wider than 200
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
    childAspectRatio: 0.75,
  ),
  itemCount: products.length,
  itemBuilder: (context, i) => ProductCard(product: products[i]),
)

A phone shows two columns, a tablet four or five, a desktop more, with no extra code.

Shortcuts for small grids #

GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16),
  mainAxisSpacing: 16,
  crossAxisSpacing: 16,
  children: const [
    Card(child: Center(child: Text('Orders'))),
    Card(child: Center(child: Text('Customers'))),
    Card(child: Center(child: Text('Products'))),
    Card(child: Center(child: Text('Reports'))),
  ],
)

GridView.extent(maxCrossAxisExtent: 200, children: [...]) is the adaptive equivalent. Both build all children at once, so keep them for small, fixed sets.

A product card for a grid #

class ProductCard extends StatelessWidget {
  const ProductCard({super.key, required this.name, required this.price, required this.imageUrl});

  final String name;
  final String price;
  final String imageUrl;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Image.network(imageUrl, width: double.infinity, fit: BoxFit.cover),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(name, maxLines: 1, overflow: TextOverflow.ellipsis),
                const SizedBox(height: 4),
                Text(price, style: const TextStyle(fontWeight: FontWeight.bold)),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

The image is in an Expanded, so it absorbs whatever height is left after the text.

Wrap: items that flow onto the next line #

When items have different widths, such as tags or filter chips, a grid is the wrong tool. Wrap lays children out in a line and starts a new line when it runs out of room.

Wrap(
  spacing: 8,     // gap between items
  runSpacing: 8,  // gap between lines
  children: [
    for (final tag in ['Dart', 'Flutter', 'Firebase', 'State management', 'UI'])
      Chip(label: Text(tag)),
  ],
)

Wrap does not scroll and builds all its children, so use it for a modest number of items.

Grids of items with different heights #

Standard grids need equal cells. For a Pinterest-style layout, use the flutter_staggered_grid_view package.

Try it yourself #

Build a category screen: a grid with a maximum cell width of 180, where each cell is a card with a large icon and a label. Above it, add a Wrap of filter chips.

Practise in the playground Updated by Santosh Adhikari