How Slivers are made — Part 1

Tomek Polański
Nov 24, 2019 · 4 min read

The best way to learn how to write your own Sliver is to know how existing Slivers are made.

I would split Slivers into groups by their complexity:

Single child Slivers

Slivers with one child that provide a special effect

Slivers containing multiple children

To understand Slivers, you need to understand Sliver constraints and geometry.

SliverConstraints & SliverGeometry

Usually, you are using Widgets that are based on RenderBox — to layout them on-screen, BoxConstraints are passed to RenderBox and its size is calculated.

Slivers are more complicated — RenderBox widgets do not care where there are on the screen, but slivers do. They need to know where they are in the scrollable space.

Slivers use SliverConstraints (RenderBox uses BoxConstraints) as inputs and calculate SliverGeometry (RenderBox calculates size).

Writing your first sliver: SliverToBoxAdapter

SliverToBoxAdapter simply adapts Widgets that are based on RenderBox, to a Sliver that you can use in a scroll view.

Every Sliver consists of aWidget and aRenderSliver.

For SliverToBoxAdapter, the widget part is pretty simple — the crucial part is creating RenderSliver, in this case, RenderSliverToBoxAdapter.

class SliverToBoxAdapter extends SingleChildRenderObjectWidget {  const SliverToBoxAdapter({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderSliverToBoxAdapter createRenderObject(BuildContext context) => RenderSliverToBoxAdapter();
}

The interesting part is RenderSliverToBoxAdapter which has only one method performLayout. Let’s diagnose it line by line:

@override
void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}

In case there is no child, we skip all the calculations.

child.layout(constraints.asBoxConstraints(), parentUsesSize: true);

We perform layout on the child using SliverConstraints, we are converted into BoxConstraints.

We specify that the parent uses a child’s size — if the child resizes, then the sliver itself will redo layout calculation.

double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.size.width;
break;
case Axis.vertical:
childExtent = child.size.height;
break;
}

Depending on the orientation of the sliver, we calculate childExtent which is the space that the child should take — if the list is vertical, then it’s child’s height.

final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);

Here we are calculating the visible child’s size. In case the child is only 50% visible, this value will be 50% of childExtend’s.

This value is assigned to Geometry.paintExtent (interactive example)

final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);

To understand cacheExtent we need to talk about viewport caching.

Image for post
Image for post
Slivers (yellow), cache area (green), visible area (red)

In the image

  • The yellow space within the black frame is an area populated by all the Slivers.
  • The red space is a visible area — to see what is in the yellow space, you need to scroll.
  • The green space is the caching area.

Slivers in the yellow area are not rendered as that would be wasteful.

When scrolling the visible area (red) we need to layout and paint not visible Slivers — we need to give them enough time for the necessary calculation — therefore we start to load them a bit earlier. Whenever a Slivers enters the cache area (green), it will be laid out even thou they are not yet visible.

The Geometry.cacheExtent (interactive example) property states how much of the sliver is in the cache (green) area.

geometry = SliverGeometry(
scrollExtent: childExtent,
paintExtent: paintedChildSize,
cacheExtent: cacheExtent,
maxPaintExtent: childExtent,
hitTestExtent: paintedChildSize,
hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
setChildParentData(child, constraints, geometry);

Applies geometry to the underlying render object.

Coming up

In the next article, we are implementing more complex SliverFillRemaining.

* In RenderBox you need to return intrinsic min/max dimensions but that we can simplify the concept for the purpose of this article

Flutter Community

Articles and Stories from the Flutter Community

Tomek Polański

Written by

Passionate mobile developer. One thing I like more than learning new things: sharing them

Flutter Community

Articles and Stories from the Flutter Community

Tomek Polański

Written by

Passionate mobile developer. One thing I like more than learning new things: sharing them

Flutter Community

Articles and Stories from the Flutter Community

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store