Flutter Community
Published in

Flutter Community

How Slivers are made

Photo by amirali mirhashemian on Unsplash

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:

Easy

Single child Slivers

Medium

Slivers with one child that provide a special effect

Hard

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. This is the full source code, but let’s go thru it line by line later:

void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}
final SliverConstraints constraints = this.constraints;
child.layout(constraints.asBoxConstraints(), parentUsesSize: true); final double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child!.size.width;
break;
case Axis.vertical:
childExtent = child!.size.height;
break;
}
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);

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

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, and they 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 the 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.

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 if 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

--

--

--

Articles and Stories from the Flutter Community

Recommended from Medium

Programmatic ways to create App roles in Azure AD

Database image

Common Programming Concepts

How to install Kubernetes Cluster on Container Linux (CoreOS)

Install Ubuntu MATE 18.04 and ROS on Raspberry Pi 3 B+

How to upgrade Feren OS 2016.X, 2017.0 and 2017–2018 Snapshots to a newer Feren OS base

How To Be An Efficient Programmer.

CloudFormation Exports and Imports

Getting started with JuiceFS using TiKV database

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
Tomek Polański

Tomek Polański

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

More from Medium

My Flutter Islamic Calendar App

Provider state management in some easy steps :

Stream Location in Flutter

Flutter MethodChannel