Skip to content

GridView

Understand how to create scrollable grid layouts in Flutter.


What is it?

GridView is a scrollable widget that displays items in a 2D grid layout. It's perfect for displaying collections of items like photos, products, icons, or any content that benefits from a grid arrangement. GridView provides multiple constructors for different use cases and efficient lazy loading.


Why does it exist?

GridView exists to:

  • Display items in a grid pattern
  • Create gallery and collection views
  • Support responsive grid layouts
  • Handle large datasets efficiently
  • Enable custom grid configurations
  • Create visually organized content
  • Support both fixed and dynamic grid sizes

GridView Constructors

Multiple constructors for different grid needs.

// 1. GridView.count() - Fixed number of columns
class CountGridView extends StatelessWidget {
  const CountGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 3, // 3 columns
      padding: const EdgeInsets.all(8),
      crossAxisSpacing: 8,
      mainAxisSpacing: 8,
      children: [
        for (int i = 0; i < 20; i++)
          Container(
            color: Colors.blue[100 * (i % 9 + 1)],
            child: Center(child: Text('${i + 1}')),
          ),
      ],
    );
  }
}

// 2. GridView.builder() - Efficient for large grids
class BuilderGridView extends StatelessWidget {
  const BuilderGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
      ),
      itemCount: 50,
      itemBuilder: (context, index) {
        return Container(
          decoration: BoxDecoration(
            color: Colors.green[100 * (index % 9 + 1)],
            borderRadius: BorderRadius.circular(8),
          ),
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(
                  Icons.star,
                  color: Colors.yellow[700],
                ),
                const SizedBox(height: 4),
                Text('Item ${index + 1}'),
              ],
            ),
          ),
        );
      },
    );
  }
}

// 3. GridView.extent() - Maximum cross-axis extent
class ExtentGridView extends StatelessWidget {
  const ExtentGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.extent(
      maxCrossAxisExtent: 150, // Max width per item
      padding: const EdgeInsets.all(8),
      crossAxisSpacing: 8,
      mainAxisSpacing: 8,
      children: [
        for (int i = 0; i < 20; i++)
          Container(
            color: Colors.red[100 * (i % 9 + 1)],
            child: Center(child: Text('${i + 1}')),
          ),
      ],
    );
  }
}

// 4. GridView.custom() - Custom grid delegate
class CustomGridView extends StatelessWidget {
  const CustomGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.custom(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
      ),
      childrenDelegate: SliverChildListDelegate(
        [
          for (int i = 0; i < 20; i++)
            Container(
              color: Colors.orange[100 * (i % 9 + 1)],
              child: Center(child: Text('${i + 1}')),
            ),
        ],
      ),
    );
  }
}

What's happening here? - GridView.count: fixed number of columns - GridView.builder: efficient for large grids - GridView.extent: max width per item - GridView.custom: custom child delegate


GridView with Different Aspect Ratios

Custom aspect ratios for grid items.

// Different aspect ratios
class AspectRatioGridView extends StatelessWidget {
  const AspectRatioGridView({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Square items (1:1)
        Expanded(
          child: GridView.count(
            crossAxisCount: 3,
            padding: const EdgeInsets.all(8),
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
            childAspectRatio: 1.0, // Square
            children: [
              for (int i = 0; i < 6; i++)
                Container(
                  color: Colors.blue[100 * (i % 9 + 1)],
                  child: const Center(child: Text('1:1')),
                ),
            ],
          ),
        ),

        // 2. Wide items (2:1)
        Expanded(
          child: GridView.count(
            crossAxisCount: 2,
            padding: const EdgeInsets.all(8),
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
            childAspectRatio: 2.0, // Wide
            children: [
              for (int i = 0; i < 4; i++)
                Container(
                  color: Colors.green[100 * (i % 9 + 1)],
                  child: const Center(child: Text('2:1')),
                ),
            ],
          ),
        ),

        // 3. Tall items (1:2)
        Expanded(
          child: GridView.count(
            crossAxisCount: 2,
            padding: const EdgeInsets.all(8),
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
            childAspectRatio: 0.5, // Tall
            children: [
              for (int i = 0; i < 4; i++)
                Container(
                  color: Colors.red[100 * (i % 9 + 1)],
                  child: const Center(child: Text('1:2')),
                ),
            ],
          ),
        ),
      ],
    );
  }
}

What's happening here? - childAspectRatio controls item proportions - 1.0 = square - >1.0 = wider - <1.0 = taller


GridView with Different Grid Delegate

Custom grid delegates for advanced layouts.

// Different grid delegates
class GridDelegateExamples extends StatelessWidget {
  const GridDelegateExamples({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Fixed cross axis count
        Expanded(
          child: GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
            ),
            itemCount: 12,
            itemBuilder: (context, index) {
              return Container(
                color: Colors.blue[100 * (index % 9 + 1)],
                child: Center(child: Text('$index')),
              );
            },
          ),
        ),

        // 2. Max cross axis extent
        Expanded(
          child: GridView.builder(
            gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 100,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
            ),
            itemCount: 12,
            itemBuilder: (context, index) {
              return Container(
                color: Colors.green[100 * (index % 9 + 1)],
                child: Center(child: Text('$index')),
              );
            },
          ),
        ),
      ],
    );
  }
}

What's happening here? - SliverGridDelegateWithFixedCrossAxisCount: fixed columns - SliverGridDelegateWithMaxCrossAxisExtent: adaptive columns - Custom delegates for different needs


Real-World Examples

Common patterns using GridView.

// 1. Photo gallery
class PhotoGallery extends StatelessWidget {
  const PhotoGallery({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Photo Gallery')),
      body: GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 2,
          mainAxisSpacing: 2,
        ),
        itemCount: 30,
        itemBuilder: (context, index) {
          return Container(
            color: Colors.blue[100 * (index % 9 + 1)],
            child: Stack(
              fit: StackFit.expand,
              children: [
                // Image placeholder
                const Center(
                  child: Icon(
                    Icons.image,
                    color: Colors.white,
                    size: 30,
                  ),
                ),
                // Photo count badge
                if (index == 5)
                  Positioned(
                    bottom: 4,
                    right: 4,
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 6,
                        vertical: 2,
                      ),
                      decoration: const BoxDecoration(
                        color: Colors.black54,
                        borderRadius: BorderRadius.all(Radius.circular(4)),
                      ),
                      child: const Text(
                        '+10',
                        style: TextStyle(color: Colors.white, fontSize: 12),
                      ),
                    ),
                  ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// 2. Product grid
class ProductGrid extends StatelessWidget {
  const ProductGrid({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Products')),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
          childAspectRatio: 0.7,
        ),
        itemCount: 20,
        itemBuilder: (context, index) {
          return Card(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Product image
                Expanded(
                  child: Container(
                    width: double.infinity,
                    color: Colors.blue[100 * (index % 9 + 1)],
                    child: const Center(
                      child: Icon(
                        Icons.shopping_bag,
                        color: Colors.white,
                        size: 40,
                      ),
                    ),
                  ),
                ),
                // Product info
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Product ${index + 1}',
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 14,
                        ),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          Text(
                            '\$${(index + 1) * 19.99}',
                            style: const TextStyle(
                              color: Colors.blue,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(width: 8),
                          Text(
                            '\$${(index + 1) * 29.99}',
                            style: TextStyle(
                              color: Colors.grey[600],
                              fontSize: 12,
                              decoration: TextDecoration.lineThrough,
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          const Icon(
                            Icons.star,
                            color: Colors.yellow,
                            size: 16,
                          ),
                          const Text(
                            '4.5',
                            style: TextStyle(fontSize: 12),
                          ),
                          const SizedBox(width: 4),
                          Text(
                            '(${(index + 1) * 100})',
                            style: TextStyle(
                              color: Colors.grey[600],
                              fontSize: 12,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// 3. Icon grid
class IconGrid extends StatelessWidget {
  const IconGrid({super.key});

  final List<IconData> icons = const [
    Icons.home,
    Icons.search,
    Icons.settings,
    Icons.person,
    Icons.star,
    Icons.favorite,
    Icons.phone,
    Icons.email,
    Icons.camera,
    Icons.video,
    Icons.music,
    Icons.game,
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Icons')),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 4,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: icons.length,
        itemBuilder: (context, index) {
          return Container(
            decoration: BoxDecoration(
              color: Colors.blue[50],
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.blue[200]!),
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(
                  icons[index],
                  size: 32,
                  color: Colors.blue[700],
                ),
                const SizedBox(height: 4),
                Text(
                  'Icon ${index + 1}',
                  style: TextStyle(
                    fontSize: 10,
                    color: Colors.grey[600],
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// 4. Responsive grid
class ResponsiveGrid extends StatelessWidget {
  const ResponsiveGrid({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // Responsive crossAxisCount based on width
        int crossAxisCount;
        if (constraints.maxWidth > 800) {
          crossAxisCount = 4;
        } else if (constraints.maxWidth > 600) {
          crossAxisCount = 3;
        } else {
          crossAxisCount = 2;
        }

        return GridView.builder(
          padding: const EdgeInsets.all(8),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: crossAxisCount,
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
          ),
          itemCount: 20,
          itemBuilder: (context, index) {
            return Container(
              color: Colors.blue[100 * (index % 9 + 1)],
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Icon(
                      Icons.star,
                      color: Colors.yellow[700],
                      size: 30,
                    ),
                    Text('Item ${index + 1}'),
                    Text(
                      '${crossAxisCount} cols',
                      style: const TextStyle(fontSize: 10),
                    ),
                  ],
                ),
              ),
            );
          },
        );
      },
    );
  }
}

What's happening here? - Photo gallery with badges - Product grid with pricing - Icon grid with styling - Responsive grid with varying columns


Best Practices

Use Builder for Large Grids

// Good - Efficient for large grids
@override
Widget build(BuildContext context) {
  return GridView.builder(
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 3,
    ),
    itemCount: 1000,
    itemBuilder: (context, index) {
      return Container(color: Colors.blue);
    },
  );
}

// Bad - Inefficient for large grids
@override
Widget build(BuildContext context) {
  return GridView.count(
    crossAxisCount: 3,
    children: List.generate(1000, (index) {
      return Container(color: Colors.blue);
    }),
  );
}

Use Appropriate Aspect Ratio

// Good - Appropriate for content
@override
Widget build(BuildContext context) {
  return GridView.count(
    crossAxisCount: 2,
    childAspectRatio: 0.8, // Good for product cards
    children: products,
  );
}

Use Keys for Dynamic Grids

// Good - With keys
@override
Widget build(BuildContext context) {
  return GridView.builder(
    itemCount: items.length,
    itemBuilder: (context, index) {
      return Container(
        key: ValueKey(items[index].id),
        child: Text(items[index].name),
      );
    },
  );
}

Common Mistakes

Wrong Aspect Ratio

Wrong:

// Items too tall or too wide
GridView.count(
  crossAxisCount: 3,
  childAspectRatio: 0.3, // Too tall
  children: items,
)

Correct:

// Reasonable aspect ratio
GridView.count(
  crossAxisCount: 3,
  childAspectRatio: 1.0, // Square
  children: items,
)

Not Using Builder for Large Data

Wrong:

// All items built at once
GridView.count(
  crossAxisCount: 3,
  children: List.generate(1000, (index) {
    return Container(color: Colors.blue);
  }),
)

Correct:

// Lazy loading
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  itemCount: 1000,
  itemBuilder: (context, index) {
    return Container(color: Colors.blue);
  },
)


Summary

GridView displays items in a 2D grid layout with multiple constructors for different needs. Use GridView.count for fixed columns, GridView.builder for large datasets, and GridView.extent for adaptive columns. GridView is perfect for galleries, product displays, and any grid-based content.


Next Steps


Did You Know?

  • GridView.builder builds items lazily
  • childAspectRatio controls item proportions
  • GridView can scroll horizontally with scrollDirection
  • GridView.extent adapts to screen width
  • GridView supports custom grid delegates
  • Keys improve grid performance
  • GridView is used for photo galleries
  • GridView can be nested with other widgets