Dart Tutorial

Flutter Lesson 21 of 83 3 min read

Scrolling, SingleChildScrollView and Slivers in Flutter

Make screens scroll in Flutter with SingleChildScrollView and CustomScrollView. Learn slivers, collapsing app bars and pull to refresh.

On this page

Content that does not fit the screen needs to scroll. Flutter has three tools, for three situations.

SituationUse
A fixed layout that might be too tallSingleChildScrollView
A long list or grid of similar itemsListView, GridView
Several different sections scrolling togetherCustomScrollView with slivers

SingleChildScrollView #

Wrap a Column in it and the overflow error disappears. The most common use is a form, which must scroll when the keyboard opens.

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Sign up')),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              for (final label in ['Name', 'Email', 'Phone', 'City', 'Password'])
                Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: label,
                      border: const OutlineInputBorder(),
                    ),
                  ),
                ),
              FilledButton(onPressed: () {}, child: const Text('Create account')),
            ],
          ),
        ),
      ),
    ),
  );
}

It builds everything inside it at once, so do not use it for long lists.

Inside a scroll view, Expanded and Spacer do not work along the scroll direction, because the available height is unlimited.

The problem slivers solve #

Suppose a page has a header, a horizontal row of categories, a grid of products and then a list of reviews, all scrolling as one. Nesting a ListView inside a SingleChildScrollView either throws an error or forces shrinkWrap, which builds every item.

A CustomScrollView takes a list of slivers: pieces of scrollable content that are built lazily and scroll together as a single surface.

CustomScrollView #

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          slivers: [
            const SliverAppBar.large(title: Text('Shop')),
            const SliverToBoxAdapter(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Text('Popular', style: TextStyle(fontSize: 20)),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              sliver: SliverGrid.builder(
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  mainAxisSpacing: 12,
                  crossAxisSpacing: 12,
                ),
                itemCount: 6,
                itemBuilder: (context, i) => Card(child: Center(child: Text('Product $i'))),
              ),
            ),
            const SliverToBoxAdapter(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Text('Reviews', style: TextStyle(fontSize: 20)),
              ),
            ),
            SliverList.builder(
              itemCount: 30,
              itemBuilder: (context, i) => ListTile(
                leading: const Icon(Icons.person),
                title: Text('Review $i'),
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

The slivers you will use #

SliverEquivalent of
SliverList.builder, SliverList.separatedListView.builder
SliverGrid.builderGridView.builder
SliverToBoxAdapterWraps one ordinary widget
SliverPaddingPadding
SliverAppBarAn app bar that scrolls, collapses or floats
SliverFillRemainingFills the rest of the screen, for empty states

Only slivers may go in the slivers list. Wrap an ordinary widget in SliverToBoxAdapter.

SliverAppBar #

PropertyBehaviour
pinned: trueThe bar stays visible at the top when collapsed
floating: trueThe bar reappears as soon as you scroll up
snap: trueWith floating, snaps fully in or out
expandedHeightHeight when fully expanded
flexibleSpaceContent that collapses, such as a cover image

SliverAppBar.medium and SliverAppBar.large give the Material 3 collapsing titles.

Pull to refresh #

Wrap any scrollable in RefreshIndicator.

RefreshIndicator(
  onRefresh: () async {
    await _reload(); // the spinner stays until this future completes
  },
  child: ListView.builder(
    itemCount: _items.length,
    itemBuilder: (context, i) => ListTile(title: Text(_items[i])),
  ),
)

If the list can be short or empty, give it physics: const AlwaysScrollableScrollPhysics() so it can still be pulled.

Scroll behaviour #

ListView(
  physics: const BouncingScrollPhysics(),        // iOS-style bounce
  // physics: const ClampingScrollPhysics(),     // Android-style stop
  // physics: const NeverScrollableScrollPhysics(), // disable scrolling
  keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
  children: const [],
)

By default Flutter uses the right physics for each platform.

Showing a scrollbar #

Scrollbar(
  child: ListView.builder(itemCount: 100, itemBuilder: (c, i) => Text('Row $i')),
)

On desktop and web a scrollbar appears automatically.

Try it yourself #

Build a profile page with a CustomScrollView: a SliverAppBar with an expandedHeight of 200 and a cover image in flexibleSpace, a SliverToBoxAdapter with a short bio, and a SliverGrid of photos.

Practise in the playground Updated by Santosh Adhikari