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

Scrollable / Slivers

Flutter scrollable widgets: ListView, GridView, SingleChildScrollView, CustomScrollView with slivers, and the patterns for smooth scrolling.

Flutter — scrollable widgets

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

// ===== ListView (vertical by default) =====
ListView(
  children: [
    for (final t in titles) ListTile(title: Text(t)),
  ],
);

// ListView.builder for lazy (don't build all items up front):
ListView.builder(
  itemCount: items.length,
  itemBuilder: (ctx, i) => ListTile(title: Text(items[i])),
);

// ListView.separated for dividers between items:
ListView.separated(
  itemCount: items.length,
  separatorBuilder: (_, __) => const Divider(),
  itemBuilder: (ctx, i) => ListTile(title: Text(items[i])),
);

// ===== GridView =====
GridView.count(crossAxisCount: 3, children: [...]);

GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, mainAxisSpacing: 4, crossAxisSpacing: 4),
  itemCount: photos.length,
  itemBuilder: (ctx, i) => Image.network(photos[i]),
);

// Responsive grid (max width per item):
GridView.builder(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 220, childAspectRatio: 3/4),
  itemCount: items.length,
  itemBuilder: ...,
);

// ===== SingleChildScrollView (one scrollable child) =====
SingleChildScrollView(
  child: Column(children: [
    HeaderWidget(),
    Form(...),
    BigImage(),
  ]),
);

// Use when the child is NOT a long list. For long lists, use ListView.builder.

// ===== CustomScrollView with slivers =====
CustomScrollView(
  slivers: [
    const SliverAppBar(
      title: Text('Feed'),
      floating: true,
      snap: true,
      pinned: false,
      expandedHeight: 200,
      flexibleSpace: FlexibleSpaceBar(background: Image.asset('header.jpg', fit: BoxFit.cover)),
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (ctx, i) => ListTile(title: Text(items[i])),
        childCount: items.length,
      ),
    ),
    SliverGrid(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
      delegate: SliverChildBuilderDelegate(
        (ctx, i) => Image.network(photos[i]),
        childCount: photos.length,
      ),
    ),
  ],
);

// ===== Pull-to-refresh =====
RefreshIndicator(
  onRefresh: () async { await fetchItems(); },
  child: ListView.builder(
    physics: const AlwaysScrollableScrollPhysics(),
    itemCount: items.length,
    itemBuilder: ...,
  ),
);

// ===== Infinite scroll =====
ListView.builder(
  controller: scrollController,   // listen for end-of-list
  itemCount: items.length + 1,
  itemBuilder: (ctx, i) {
    if (i >= items.length) {
      // Load more here
      return const Center(child: CircularProgressIndicator());
    }
    return ListTile(title: Text(items[i]));
  },
);

// In initState:
// scrollController.addListener(() {
//   if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) {
//     loadMore();
//   }
// });

// ===== Horizontal scroll =====
ListView.builder(
  scrollDirection: Axis.horizontal,
  itemCount: chips.length,
  itemBuilder: (ctx, i) => Padding(padding: const EdgeInsets.symmetric(horizontal: 4), child: Chip(label: Text(chips[i]))),
);

// ===== Patterns to internalise =====
// - .builder over child lists for long content
// - CustomScrollView + slivers for mixed scrolling content
// - RefreshIndicator + AlwaysScrollableScrollPhysics
// - Listen to ScrollController for infinite loading

// ===== Pitfalls =====
// - Putting ListView inside a Column without bounded height -> layout error
// - Using SingleChildScrollView for a long list -> all items built up front
// - Forgetting to dispose ScrollController -> leaks
// - Nesting scrollables on the same axis -> gesture confusion

Why it matters

Pick the scrollable that matches the content shape. ListView.builder for lazy lists, GridView for grids, SingleChildScrollView for short forms, CustomScrollView + slivers for mixed layouts with collapsing headers. RefreshIndicator + ScrollController cover pull-to-refresh and infinite scroll.

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

Example

Example
CustomScrollView(slivers: [
    SliverAppBar(title: Text('Header'), floating: true),
    SliverList.builder(
        itemCount: 50,
        itemBuilder: (_, i) => ListTile(title: Text('Row $i')),
    ),
])
Try it Yourself »

Discussion

Loading…