Slivers and CustomScrollView in Flutter

Scrolling through the screen inside any mobile application is one of the most common user behaviors. User interfaces therefore require building scrollable contents. For this reason numerous scrolling techniques are popular among mobile development frameworks. Flutter itself provides you with a number of different type of scrolling widgets for this very purpose. Some of them include ListView, GridView, CustomScrollView, SingleChildScrollView, PageView, Scrollable, Scrollbar etc. Among these the most frequently used ones include the ListView and the GridView. They effortlessly display a scrollable list or grid onto the screen. Scrolling however, isn’t just limited to these two, there are other widgets that assist in providing a more advanced, customized and better scrolling ability. In this article we are going to discuss about one such technique and widget: The Sliver and CustomScrollView in Flutter.

What is a Sliver in Flutter?

The official Flutter docs define Sliver as a portion of a scrollable area that you can define to behave in a special way i.e. we make use of slivers to achieve custom scrolling effects. It can be regarded as an interface for providing a finer grain control on implementing scrollable area on the screen. Slivers are what work under the hood to implement different scrollable widgets including listview and gridview as well. Slivers work by lazily building their views when the widgets enter the viewport due to which they can be used to effectively scroll through a number of children without worrying about memory issues.  As they are based on the viewport, it can change their shape, size, and extent based on several events and offset.

Typical scenarios for implementation of Slivers include the following :

1. Adding an appbar which disappears on scroll or that changes color and size with scroll

2. Scrolling a list of items and a grid of items as a single unit.

3. Collapsing list with headers.

How are Slivers used?

Slivers incorporate a number of different kind of Sliver Components in the form of Widgets. They can include : SliverAppBar, SliverList,SliverGrid, SliverToBoxAdapter,SliverPadding , SliverFillRemaining and so on.

All sliver components go inside a CustomScrollView. The components are assigned to the slivers argument of the CustomScrollView class in the form of a List. The list of slivers can be combined in any which way to obtain a desirable custom scrollable area. For instance, to create a ListView equivalent using slivers and CustomScrollView we can simply place a SliverList within a CustomScrollView and there you go you have yourself a ListView.

Eg: A ListView and its Sliver equivalent is demonstrated in the code below.

//An example for a ListView

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final height = MediaQuery.of(context).size.height;
    final width = MediaQuery.of(context).size.width;

    return Scaffold(
      appBar: AppBar(
        title: const Text('ListView'),
      ),
      body: Container(
        height: height,
        width: width,
        child: ListView(
          children: const [
            ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
          ],
        ),
      ),
    );
  }
}
// Sliver equivalent for above code snippet

class HomePageTwo extends StatefulWidget {
  const HomePageTwo({Key? key}) : super(key: key);

  @override
  _HomePageTwoState createState() => _HomePageTwoState();
}

class _HomePageTwoState extends State<HomePageTwo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverList(
              delegate: SliverChildListDelegate([
            const ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            const ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            const ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            const ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
            const ListTile(
              leading: Icon(Icons.person),
              title: Text('John Mayer'),
              subtitle: Text('singer'),
            ),
          ])),
        ],
      ),
    );
  }
}

Output for above code:

CustomScrollView in Flutter

The CustomScrollView is a ScrollView type widget that lets you create different scrolling effects using Slivers such as expanding headers, lists, grids etc. Sliver components are widgets that go inside slivers that necessarily produce RenderSliver objects. The CustomScrollView is solely responsible for the scrolling behaviour of the entire widget and rendering its children as they enter the viewport.

The code snippet below shows how a CustomScrollView is implemented.

CustomScrollView(slivers: [
  SliverAppBar(...),
  SliverPadding(...),
  SliverList(...),
  SliverGrid(...),
]

Leave the rest to CustomScrollView, it will handle the scroll behavior and create all of its children when they come inside the viewport.

Introduction to Fundamental Slivers

In this section, we briefly discuss a few fundamental Sliver Components frequently used with CustomScrollViews.

SliverAppBar

SliverAppBar is similar to the Material Design Appbar, the only difference being the fact that SliverAppBar go inside CustomScrollView and Appbar do not. It is typically placed as the first Sliver component inside a CustomScrollView. It holds all properties of a Material Appbar and more. SliverAppBar come with additional properties including : pinned, floating, expandedHeight , snap etc that help customize its behavior.

Significant Properties:

expandedHeight: denotes the size of the appbar when expanded completely.

pinned: determines whether the appbar remains visible at the top of the screen upon scrolling down.ther the appbar remains visible at the top of the screen upon scrolling down.

floating: determines whether or not the appbar must be immediately visible as soon as the user scrolls towards the app bar.

snap: setting this property to true makes the entire appbar visible at once when the user scrolls towards the appbar. It depends on the floating property to function correctly.

flexibleSpace: it typically provides a background and different collapseModes to the Appbar. It also has a title property that transforms its position with respect to the scrolling motion.

SliverList

SliverList is equivalent to the ListView widget. These are used to build a linear list of items. SliverList takes a delegate parameter. The delegate parameter is provided with the items of items that scroll to view. Delegates may be of two variations: SliverChildListDelegate and SliverChildBuilderDelegate. For rendering the item list at once we use the SliverChildDelegate whereas for lazy loading of the itemList we prefer SliverChildBuilderDelegate.

SliverList takes a delegate parameter which provides the items in the list as they scroll into view. You can specify the actual list of children with a SliverChildListDelegate Or build them lazily with a SliverChildBuilderDelegate.

SliverGrid

SliverGrid is the equivalent to the GridView widget. It places their children in a 2D array onto the screen with the help of the delegate parameter similar to the SliverList. In addition to this SliverGrids also need to specify their cross axis dimension for the grid. SliverGrids come with a variety of constructors to deliver the grid layout that you desire. The constructors include the following:

1. The count Constructor: used to specify number of items in the horizontal axis.

SliverGrid.count(children: scrollItems, crossAxisCount: 3)

2. The extent constructor: used to specify the maximum width of items to determine how many should fit across a grid.

SliverGrid.extent(children: scrollItems, maxCrossAxisExtent: 75.0) // 75 logical pixels

3. The default constructor: The default constructor is the one that takes in an additional gridDelegate parameter. The gridDelegate takes a SliverGridDelegateWithFixedCrossAxisCount class that essentially helps fix the number of items in the horizontal axis.

//Re-implementing the above SliverGrid.count example:
SliverGrid(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 4,
  ),
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return new Container(
        margin: EdgeInsets.all(5.0),
        color: Colors.cyan[100 * (index % 8)],
        height: 150.0);
    }
);

Example:

Let us now look at a simplified example of a CustomScrollView implementing SliverAppbar, SliverlIst and a SliverGrid:

class SliverDemo extends StatefulWidget {
  const SliverDemo({Key? key}) : super(key: key);

  @override
  _SliverDemoState createState() => _SliverDemoState();
}

class _SliverDemoState extends State<SliverDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            pinned: true,
            snap: false,
            floating: true,
            expandedHeight: 100.0,
            flexibleSpace: FlexibleSpaceBar(
              collapseMode: CollapseMode.parallax,
              title: Text('SliverAppBar'),
            ),
            backgroundColor: Colors.cyan,
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  height: 50,
                  alignment: Alignment.center,
                  color: index.isEven ? Colors.green[300] : Colors.white,
                  child: Text('List item :  $index'),
                );
              },
              childCount: 15,
            ),
          ),
          SliverGrid(
            gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 200.0,
              mainAxisSpacing: 10.0,
              crossAxisSpacing: 10.0,
              childAspectRatio: 4.0,
            ),
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  alignment: Alignment.center,
                  color: Colors.cyan[100 * (index % 8)],
                  child: Text('grid item $index'),
                );
              },
              childCount: 15,
            ),
          ),
        ],
      ),
    );
  }
}

Output for the above code:

This was all for a brief introduction to Slivers and CustomScrollView in Flutter for now. Hope this article was helpful.

Also Read: Lifecycle methods of Flutter Widgets.

Happy coding!

Tags:

Samriddhi is a curious and problem solving software developer. A go-getter and a passionate team player who loves converting the designs to high class functional codes.