SliverAppBar, SliverList & SliverGrid
Introduction to Slivers
Slivers are the building blocks of scrollable areas in Flutter. While widgets like ListView and GridView are convenient, they are actually built on top of slivers. When you need advanced scrolling effects — collapsing headers, sticky elements, or mixed scroll layouts — you work directly with slivers inside a CustomScrollView.
CustomScrollView composes multiple slivers into one unified scrollable surface.
CustomScrollView Basics
The CustomScrollView widget takes a list of slivers in its slivers property. Every child must be a sliver widget (prefixed with Sliver). You cannot mix regular widgets and slivers directly.
Basic CustomScrollView Structure
class MyScrollPage extends StatelessWidget {
const MyScrollPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// Sliver widgets go here
const SliverAppBar(
title: Text('My App'),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text('Item \$index'),
),
childCount: 50,
),
),
],
),
);
}
}
SliverAppBar
The SliverAppBar is one of the most powerful sliver widgets. It creates an app bar that can expand, collapse, float, pin, and snap as the user scrolls. It replaces the standard AppBar when used inside a CustomScrollView.
Key Properties
- expandedHeight — The height of the app bar when fully expanded.
- floating — If
true, the app bar becomes visible as soon as the user scrolls up, even if they haven’t reached the top. - pinned — If
true, the collapsed app bar remains visible at the top. - snap — If
true(requiresfloating: true), the app bar snaps open or closed rather than partially showing. - flexibleSpace — A widget that stretches to fill the expanded area, typically a
FlexibleSpaceBar.
SliverAppBar with FlexibleSpaceBar
class CollapsingHeaderPage extends StatelessWidget {
const CollapsingHeaderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 250.0,
floating: false,
pinned: true,
snap: false,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Mountain View'),
centerTitle: true,
background: Image.network(
'https://picsum.photos/800/400',
fit: BoxFit.cover,
),
collapseMode: CollapseMode.parallax,
),
actions: [
IconButton(
icon: const Icon(Icons.share),
onPressed: () {},
),
],
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
leading: CircleAvatar(child: Text('\${index + 1}')),
title: Text('Item \${index + 1}'),
subtitle: const Text('Subtitle text'),
),
childCount: 30,
),
),
],
),
);
}
}
CollapseMode.parallax for a parallax scrolling effect on the background image, CollapseMode.pin to keep the background stationary, or CollapseMode.none for no special behavior.
Floating and Snapping
The floating and snap properties work together to control how the app bar reappears when the user scrolls up mid-list.
Floating + Snapping SliverAppBar
SliverAppBar(
expandedHeight: 200.0,
floating: true,
pinned: false,
snap: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Quick Access Bar'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.deepPurple, Colors.indigo],
),
),
),
),
)
snap: true requires floating: true. If you set snap: true without floating: true, Flutter will throw an assertion error at runtime.
SliverList
SliverList displays a linear list of children inside a CustomScrollView. Flutter 3.x provides convenient constructors that simplify creation.
SliverList.builder
The most common constructor, equivalent to ListView.builder. It lazily builds children on demand.
SliverList.builder Example
final List<String> cities = [
'London', 'Paris', 'Tokyo', 'New York',
'Sydney', 'Dubai', 'Berlin', 'Rome',
];
SliverList.builder(
itemCount: cities.length,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
child: ListTile(
leading: const Icon(Icons.location_city),
title: Text(cities[index]),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// Navigate to city detail
},
),
);
},
)
SliverList.separated
This constructor adds a separator widget between each item, just like ListView.separated.
SliverList.separated Example
SliverList.separated(
itemCount: 20,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text(
'\${index + 1}',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Contact \${index + 1}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'contact\${index + 1}@example.com',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
],
),
);
},
separatorBuilder: (context, index) => const Divider(
height: 1,
indent: 72,
),
)
SliverGrid
SliverGrid lays out children in a two-dimensional grid inside a CustomScrollView. You control the grid layout with a gridDelegate.
SliverGrid with SliverGridDelegateWithFixedCrossAxisCount
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(
child: Text(
'Tile \${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
);
},
childCount: 20,
),
)
SliverGridDelegateWithMaxCrossAxisExtent when you want each tile to have a maximum width and let Flutter calculate the number of columns automatically. This is great for responsive grid layouts.
SliverFixedExtentList
When all items in a list have the same fixed height, SliverFixedExtentList is more efficient than SliverList because Flutter can calculate item positions without measuring each child.
SliverFixedExtentList Example
SliverFixedExtentList(
itemExtent: 72.0,
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade200,
),
),
),
child: Row(
children: [
Icon(
Icons.music_note,
color: Colors.blue.shade400,
),
const SizedBox(width: 16),
Expanded(
child: Text(
'Track \${index + 1} - Song Title',
style: const TextStyle(fontSize: 16),
),
),
Text(
'3:\${(index * 7 % 60).toString().padLeft(2, '0')}',
style: TextStyle(color: Colors.grey[600]),
),
],
),
);
},
childCount: 100,
),
)
SliverAnimatedList
SliverAnimatedList provides animated insertions and removals within a CustomScrollView. It works like AnimatedList but as a sliver.
SliverAnimatedList Example
class AnimatedSliverListPage extends StatefulWidget {
const AnimatedSliverListPage({super.key});
@override
State<AnimatedSliverListPage> createState() =>
_AnimatedSliverListPageState();
}
class _AnimatedSliverListPageState extends State<AnimatedSliverListPage> {
final GlobalKey<SliverAnimatedListState> _listKey =
GlobalKey<SliverAnimatedListState>();
final List<String> _items = ['Apple', 'Banana', 'Cherry'];
void _addItem() {
final index = _items.length;
_items.add('Fruit \${index + 1}');
_listKey.currentState?.insertItem(
index,
duration: const Duration(milliseconds: 400),
);
}
void _removeItem(int index) {
final removed = _items.removeAt(index);
_listKey.currentState?.removeItem(
index,
(context, animation) => SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.red.shade100,
child: ListTile(title: Text(removed)),
),
),
duration: const Duration(milliseconds: 300),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
const SliverAppBar(
title: Text('Animated Sliver List'),
pinned: true,
),
SliverAnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
child: ListTile(
title: Text(_items[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
),
),
);
},
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _addItem,
child: const Icon(Icons.add),
),
);
}
}
Practical Example: App Store Layout
Let’s combine everything into a real-world example — an app store page with a collapsing header, a horizontal featured section, and a grid of apps.
Complete App Store Layout
class AppStorePage extends StatelessWidget {
const AppStorePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// Collapsing Header
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('App Store'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue, Colors.indigo],
),
),
child: const Center(
child: Icon(
Icons.store,
size: 64,
color: Colors.white70,
),
),
),
),
),
// Section Title
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
'Top Apps',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
),
// App Grid
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverGrid(
gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.75,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 30,
backgroundColor: Colors
.primaries[index % Colors.primaries.length],
child: const Icon(
Icons.apps,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'App \${index + 1}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
5,
(i) => Icon(
Icons.star,
size: 14,
color: i < 4
? Colors.amber
: Colors.grey.shade300,
),
),
),
],
),
);
},
childCount: 12,
),
),
),
// Bottom Spacing
const SliverToBoxAdapter(
child: SizedBox(height: 32),
),
],
),
);
}
}
SliverAppBar provides collapsing headers with flexible space. SliverList and SliverGrid handle linear and grid layouts within a CustomScrollView. Use SliverFixedExtentList for performance when items share the same height, and SliverAnimatedList for animated additions and removals. Combine multiple slivers in one CustomScrollView for rich, performant scrollable interfaces.