iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

GridView

GridView lays children out in a scrollable grid. Pick a constructor that matches your data shape; reach for SliverGrid when you compose with other slivers.

Flutter — GridView

EXAMPLE
import 'package:flutter/material.dart';

class PhotoGrid extends StatelessWidget {
  final List<String> urls;
  const PhotoGrid({super.key, required this.urls});

  @override
  Widget build(BuildContext context) {
    // ===== 1. Fixed column count =====
    return GridView.count(
      crossAxisCount: 3,
      mainAxisSpacing: 4,
      crossAxisSpacing: 4,
      padding: const EdgeInsets.all(8),
      children: [
        for (final u in urls)
          Image.network(u, fit: BoxFit.cover),
      ],
    );
  }
}

// ===== 2. Builder constructor (lazy) =====
class PhotoGridBuilder extends StatelessWidget {
  final List<String> urls;
  const PhotoGridBuilder({super.key, required this.urls});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: urls.length,
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: 4,
        crossAxisSpacing: 4,
        childAspectRatio: 1,
      ),
      itemBuilder: (ctx, i) => Image.network(urls[i], fit: BoxFit.cover),
    );
  }
}

// ===== 3. Max width per item (responsive) =====
class ProductGrid extends StatelessWidget {
  final List<String> names;
  const ProductGrid({super.key, required this.names});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: names.length,
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 220,
        mainAxisSpacing: 12,
        crossAxisSpacing: 12,
        childAspectRatio: 3 / 4,
      ),
      padding: const EdgeInsets.all(16),
      itemBuilder: (ctx, i) => Card(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.image, size: 48),
            Text(names[i]),
          ],
        ),
      ),
    );
  }
}

// ===== 4. SliverGrid inside CustomScrollView =====
class FeedGrid extends StatelessWidget {
  final List<String> urls;
  const FeedGrid({super.key, required this.urls});

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        const SliverAppBar(title: Text('Feed'), floating: true),
        SliverPadding(
          padding: const EdgeInsets.all(8),
          sliver: SliverGrid(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              mainAxisSpacing: 8,
              crossAxisSpacing: 8,
            ),
            delegate: SliverChildBuilderDelegate(
              (ctx, i) => Image.network(urls[i], fit: BoxFit.cover),
              childCount: urls.length,
            ),
          ),
        ),
      ],
    );
  }
}

// ===== 5. Pull-to-refresh wrapper =====
class RefreshingGrid extends StatelessWidget {
  final Future<void> Function() onRefresh;
  final List<String> urls;
  const RefreshingGrid({super.key, required this.onRefresh, required this.urls});

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: onRefresh,
      child: GridView.builder(
        physics: const AlwaysScrollableScrollPhysics(),
        itemCount: urls.length,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
        itemBuilder: (ctx, i) => Image.network(urls[i], fit: BoxFit.cover),
      ),
    );
  }
}

// ===== Patterns to internalise =====
// - .builder for any list > 50 items (lazy is cheap; eager is not)
// - SliverGridDelegateWithMaxCrossAxisExtent for responsive layouts; columns adapt to width
// - SliverGrid + CustomScrollView when you compose with headers / lists
// - Set childAspectRatio explicitly; otherwise it defaults to 1.0 and crops content
// - Use a const constructor for the delegate to avoid re-builds

// ===== Pitfalls =====
// - GridView.count with 10k children -> builds them all up front
// - Nesting GridView in a Column without a bounded height -> layout error
// - childAspectRatio + Image with auto height -> overflows
// - Forgetting physics: AlwaysScrollableScrollPhysics() with RefreshIndicator -> can't pull-to-refresh on short lists
// - Mixing SliverGrid in a non-CustomScrollView -> compile-time error or runtime layout error

Why it matters

GridView gets you 80% of the way to anything that looks like a grid. Use .builder for lazy lists, MaxCrossAxisExtent for responsive layouts, SliverGrid when you compose with other slivers, and always set childAspectRatio. The choices map directly onto the data shape — pick the constructor that already matches it.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
GridView.count(
    crossAxisCount: 2,
    crossAxisSpacing: 8,
    children: [for (final t in tiles) Card(child: Center(child: Text(t)))],
)
Try it Yourself »

Discussion

Loading…