Slivers & Custom Scroll Views
Slivers & Custom Scroll Views
Flutter's standard scrolling widgets — ListView, GridView, SingleChildScrollView — are convenient, but they offer little control over how individual parts of a scrollable area behave. Slivers are the lower-level building blocks that power all Flutter scroll views. A sliver is a portion of a scrollable area that can change its geometry (size, position, visual effect) in response to scroll offset. By composing slivers inside a CustomScrollView, you can create sophisticated layouts such as collapsing app bars, sticky section headers, and mixed list-grid feeds.
ListView and GridView is internally implemented with slivers. When you need more than one scrollable region type — e.g., a large banner followed by a grid — you must drop down to the sliver layer and use CustomScrollView directly.CustomScrollView — The Container
CustomScrollView accepts a list of slivers in its slivers property and scrolls them as a single unified viewport. All children share the same ScrollController and scroll physics, which is why the expanding app bar and the list beneath it collapse in perfect sync.
Minimal CustomScrollView skeleton
CustomScrollView(
slivers: [
// 1. A collapsing app bar
SliverAppBar(
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('My Feed'),
background: Image.network(
'https://picsum.photos/800/400',
fit: BoxFit.cover,
),
),
),
// 2. A section of list items
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
childCount: 20,
),
),
// 3. A section of grid items
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(color: Colors.teal, child: Center(child: Text('$index')));
},
childCount: 12,
),
),
],
)
Core Sliver Widgets
SliverAppBar
SliverAppBar integrates with the scroll offset to expand and collapse a header area. Key properties:
- expandedHeight — the total height when fully expanded.
- pinned: true — keeps the toolbar visible after collapsing.
- floating: true — re-expands as soon as the user scrolls up, even mid-list.
- snap: true — used together with
floating; snaps the bar fully open or closed. - flexibleSpace: FlexibleSpaceBar — renders the expanding area (background image, title that fades or shifts).
SliverList
SliverList renders children lazily in a linear list. Use SliverChildBuilderDelegate for large or infinite lists, or SliverChildListDelegate for a small, fixed set of widgets.
SliverGrid
SliverGrid renders children in a two-dimensional grid. The gridDelegate is either SliverGridDelegateWithFixedCrossAxisCount (fixed column count) or SliverGridDelegateWithMaxCrossAxisExtent (automatic column count based on max item width).
SliverToBoxAdapter
Wraps a single normal (box) widget so it can be placed inside a CustomScrollView. Use this for standalone sections like a banner, a search bar, or a heading row that don't belong to a list or grid.
SliverPadding
Applies padding around another sliver. Equivalent to wrapping a box widget in Padding, but operates in sliver geometry.
SliverAppBar → SliverToBoxAdapter (promo banner) → SliverList (featured items) → SliverGrid (all items). Each section scrolls as one seamless experience.SliverPersistentHeader & Custom Delegates
For fully bespoke collapsing headers — ones that change layout, animate child elements, or apply custom blends as they collapse — use SliverPersistentHeader with a hand-crafted SliverPersistentHeaderDelegate.
You must subclass SliverPersistentHeaderDelegate and implement three members:
double get minExtent— the minimum height of the header (fully collapsed).double get maxExtent— the maximum height of the header (fully expanded).Widget build(BuildContext context, double shrinkOffset, bool overlapsContent)— called on every frame;shrinkOffsetgoes from 0 (expanded) tomaxExtent - minExtent(fully collapsed).bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate)— returntruewhen the delegate's configuration changes.
Custom collapsing profile header
class ProfileHeaderDelegate extends SliverPersistentHeaderDelegate {
final String username;
final String avatarUrl;
const ProfileHeaderDelegate({
required this.username,
required this.avatarUrl,
});
@override
double get minExtent => 60.0;
@override
double get maxExtent => 200.0;
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
// Compute how far along the collapse we are (0.0 = open, 1.0 = closed)
final double t = (shrinkOffset / (maxExtent - minExtent)).clamp(0.0, 1.0);
final double avatarSize = lerpDouble(80.0, 32.0, t)!;
return Container(
color: Colors.indigo,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
radius: avatarSize / 2,
backgroundImage: NetworkImage(avatarUrl),
),
const SizedBox(width: 12.0),
Text(
username,
style: TextStyle(
color: Colors.white,
fontSize: lerpDouble(22.0, 16.0, t)!,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
@override
bool shouldRebuild(covariant ProfileHeaderDelegate oldDelegate) {
return oldDelegate.username != username ||
oldDelegate.avatarUrl != avatarUrl;
}
}
// Usage inside CustomScrollView:
SliverPersistentHeader(
pinned: true,
delegate: ProfileHeaderDelegate(
username: 'Edrees Salih',
avatarUrl: 'https://picsum.photos/200',
),
),
build method of a SliverPersistentHeaderDelegate. The framework constrains the header to the range [minExtent, maxExtent], so any inner widget that demands a height outside that range will either overflow or be clipped unexpectedly.Performance Considerations
Slivers render only what is currently visible in the viewport (lazy evaluation), making them suitable for long or infinite lists. Keep these rules in mind:
- Prefer
SliverChildBuilderDelegateoverSliverChildListDelegatefor lists longer than ~20 items — the builder version builds items on demand. - Avoid nesting a
ListView(or any other shrink-wrap scroll view) inside aCustomScrollViewsliver — useSliverListinstead to keep everything in the same scroll context. - The
buildmethod of yourSliverPersistentHeaderDelegateis called on every scroll frame; keep it cheap (no heavy computation, no synchronous I/O).
Summary
Slivers give you granular control over every segment of a scrollable UI. Compose a CustomScrollView from SliverAppBar, SliverList, SliverGrid, SliverToBoxAdapter, and SliverPadding for standard needs. For bespoke collapsing headers that animate or change layout as they shrink, implement a SliverPersistentHeaderDelegate and compute your widget geometry using the shrinkOffset parameter. This combination unlocks rich, performant scroll experiences that would be impossible with ordinary box-layout widgets.