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.

4.9 (20) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

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:

  1. SliverGrid.Count: You display the children depending on a fixed number of cross-axis tiles that you pass to the constructor as crossAxisCount.
  2. 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 make SliverGrid determine how many of them should fit across the grid.
  3. 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 uses SliverChildBuilderDelegate.

SliverGrid constructors

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:

  1. Define the main and cross spacing between the box children.
  2. Give the crossAxisCount a number that constrains the count of horizontal elements.
  3. By defining the ratio of the length to the width of the PillWidget, you constraint the height of each rendered element in the grid.
  4. Transform the list of ingredients to a list of PillWidgets.
Note: 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:

Recipe Details SliverGrids

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:

  1. Define a gridDelegate by determining the maximum cross-axis extent per child.
  2. 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:

Recipe details with two grids

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:

  1. _SliverAppBarDelegate is a private class that extends the abstract class SliverPersistentHeaderDelegate and overrides the implementation of minExtent and maxExtent.
  2. The implementation of the build function only builds the expanded child widget.
  3. You also override shouldRebuild so it rebuilds when any of these three properties change: maxHeight, minHeight or child.

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:

  1. SliverSubHeader returns a SliverSubHeader with a custom configuration. You’ll use it as a subheader in RecipePage.
  2. You pass the desired minimum and maximum height to the implemented delegate class.
  3. 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: Xs 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:

Recipe Page Final

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 SliverLists and SliverGrids 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!