Slivers in Flutter: Getting Started
In this article you’ll learn about Slivers in Flutter, how they work, and use them to make a beautifully designed app for recipes. By Michael Malak.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Slivers in Flutter: Getting Started
25 mins
- Getting Started
- Understanding Slivers
- Displaying Recipe List
- SliverFixedExtentList and SliverList
- Building a Scrollable App Bar
- Reusing SliverAppBar
- Inserting a Footer to CustomScrollView
- Adjusting the Layout
- Controlling the SliverGrid Layout
- Adding the Ingredients Grid
- Adding the Numbers Grid
- Implementing SliverPersistentHeaderDelegate
- Customizing a Subheader With SliverPersistentHeader
- Where to Go From Here?
Controlling the SliverGrid Layout
You use SliverGrid
as you would use SliverList
, by giving it child boxes and getting a RenderSliver
. However, you’ll get a two-dimensional arrangement of rendered children in SliverGrid
rather than a linear arrangement. You control this arrangement using various constructors in SliverGrid
:
-
SliverGrid.Count: You display the children depending on a fixed number of cross-axis tiles that you pass to the constructor as
crossAxisCount
. -
SliverGrid.Extent: Similar to how you use
SliverFixedExtentList
for linear lists, you arrange the children so each one has a maximum cross-axis extent that you pass to the constructor. By specifying the maximum width of the items, you makeSliverGrid
determine how many of them should fit across the grid. -
SliverGrid: You use
SliverGrid
‘s default constructor and give it two delegates. The first,gridDelegate
, determines the grid’s layout, either by cross-axis count or extent. The second,SliverChildBuilderDelegate
, builds the children by determining their count and giving them an index to efficiently build the list of box children.SliverList
usesSliverChildBuilderDelegate
.
Adding the Ingredients Grid
Each recipe has a list of ingredients and a list of informational numbers, such as cooking time or number of servings. You’ll display each list in a separate SliverGrid
.
Add the following import to the top of lib/pages/recipe/recipe_page.dart:
import 'widgets/pill_widget.dart';
Then, replace // TODO: SliverGrid for recipe.ingredients
with:
SliverPadding(
padding: const EdgeInsets.all(15),
sliver: SliverGrid.count(
//1
mainAxisSpacing: 15,
crossAxisSpacing: 10,
//2
crossAxisCount: 3,
//3
childAspectRatio: 3,
//4
children: recipe.ingredients.map((e) => PillWidget(e)).toList(),
),
),
Here you:
- Define the main and cross spacing between the box children.
- Give the
crossAxisCount
a number that constrains the count of horizontal elements. - By defining the ratio of the length to the width of the
PillWidget
, you constraint the height of each rendered element in the grid. - Transform the list of ingredients to a list of
PillWidget
s.
SliverGrid.count
isn’t the most efficient way to render a SliverGrid
, as it requires building the widget list in advance. Using the default SliverGrid
constructor is considered more efficient.Build and run. You’ll see the following screen:
Adding the Numbers Grid
You’ll also add a SliverGrid
for the numbers grid. However, you’ll use its default constructor.
In the same file, replace // TODO: SliverGrid for recipe.details
with:
SliverPadding(
padding: const EdgeInsets.all(15),
sliver: SliverGrid(
// 1
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 4,
),
// 2
delegate: SliverChildBuilderDelegate(
(context, index) => PillWidget(recipe.details[index]),
childCount: recipe.details.length,
),
),
),
Here you:
- Define a
gridDelegate
by determining the maximum cross-axis extent per child. - Set a builder delegate to efficiently build the widgets only when they’re in the viewport.
Build and run. You’ll see two grids like this:
Implementing SliverPersistentHeaderDelegate
While SliverAppBar
is customizable, sometimes you need even more customization. Under the hood, SliverAppBar
is a SliverPersistentHeader
. This means you’ll find most of the properties that are in SliverAppBar
in SliverPersistentHeader
as well.
SliverPersistentHeader
takes a delegate class that extends the abstract class SliverPersistentHeaderDelegate
.
The recipe details page doesn’t have any subheader. You’ll use SliverPersistentHeader
to make elegant, persistent subheaders that expand when you scroll down but persist with a minimum height when scrolling up. Then, you’ll insert these subheaders inside RecipePage
‘s CustomListView
.
Before creating the subheader, you’ll first create the delegate that SliverPersistentHeader
accepts. Go to lib/pages/recipe/widgets/ and create a new file named sliver_sub_header.dart.
Add the following code to it:
import 'dart:math';
import 'package:flutter/material.dart';
// 1
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final double minHeight;
final double maxHeight;
final Widget child;
_SliverAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
@override
double get minExtent => minHeight;
@override
double get maxExtent => max(maxHeight, minHeight);
// 2
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
// 3
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
Here’s what’s happening in the code above:
-
_SliverAppBarDelegate
is a private class that extends the abstract classSliverPersistentHeaderDelegate
and overrides the implementation ofminExtent
andmaxExtent
. - The implementation of the build function only builds the expanded child widget.
- You also override
shouldRebuild
so it rebuilds when any of these three properties change:maxHeight
,minHeight
orchild
.
Customizing a Subheader With SliverPersistentHeader
You built the delegate. Now, you need to create your custom SliverPersistentHeader
.
While you’re still in lib/pages/recipe/widgets/sliver_sub_header.dart and above _SliverAppBarDelegate
, add:
import '../../../constants/colors.dart';
class SliverSubHeader extends StatelessWidget {
final String text;
final Color backgroundColor;
const SliverSubHeader(
{Key key, @required this.text, @required this.backgroundColor})
: assert(text != null),
assert(backgroundColor != null),
super(key: key);
@override
Widget build(BuildContext context) {
// 1
return SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
// 2
minHeight: 40,
maxHeight: 70,
// 3
child: Container(
color: backgroundColor,
child: Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.navy,
fontSize: 23,
fontWeight: FontWeight.bold),
),
),
),
),
);
}
}
Here’s a breakdown:
-
SliverSubHeader
returns aSliverSubHeader
with a custom configuration. You’ll use it as a subheader inRecipePage
. - You pass the desired minimum and maximum height to the implemented delegate class.
- The delegate gets the subheader text as a child widget and displays it in the persistent header.
Are you excited to see how the subheader turned out?
Go to lib/pages/recipe/recipe_page.dart and add an import for your SliverSubHeader
to the top, like this:
import 'widgets/sliver_sub_header.dart';
Replace all the // TODO: Subheader with text title: X
s with an equivalent instance of SliverSubHeader
widget an use X
as the value of text. For example,:
SliverSubHeader(
text: 'Instruction',
backgroundColor: recipe.itemColor,
),
Finally, for it to work as expected, you need to provide a SliverFillRemaining
at the end of CustomScrollView
. Replace // TODO: SliverFillRemaining
with:
SliverFillRemaining(
child: Container(),
),
This lets you scroll the CustomScrollView
freely in the vertical axis as you fill the space with an empty container.
Mission accomplished! Build and run. You'll get something like this:
Where to Go From Here?
Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.
Now you have a deeper understanding of what slivers are, and more importantly, when and how to use them. You used SliverList
s and SliverGrid
s along with your SliverAppBar
to have an interesting scrolling behavior.
Dive deeper in slivers by reading the Flutter documentation for RenderSliver.
If you have any questions, comments or want to show off great theming options for your app, feel free to join the discussion below!