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…