How Slivers are made: SliverFillRemaining

Tomek Polański
Flutter Community
Published in
3 min readDec 31, 2019

--

SliverFillRemaining is fairly simple yet useful — it fills the remaining scrollable space with a widget of your choice.

https://flutter-animations-cheat-sheet.codemagic.app/#/slivers/fill-remaining

SliverFillRemaining — the widget part

SliverFillRemaining differs from SliverToBoxAdapter as it accepts parameters:

const SliverFillRemaining({
this.hasScrollBody = true,
this.fillOverscroll = false,
// …
})
  • hasScrollBody allows the content of the sliver to be scrollable.
  • fillOverscroll allows the stretch behaviour when over-scrolling that you can see on iOS.

Those are passed to RenderSliver when it is created:

@override 
RenderSliverFillRemaining createRenderObject(BuildContext context) {
return RenderSliverFillRemaining(
hasScrollBody: hasScrollBody,
fillOverscroll: fillOverscroll,
);
}

When this widget is updated, the underlying RenderSliverFillRemaining not recreated — is being just updated:

@override
void updateRenderObject(BuildContext context, RenderSliverFillRemaining renderObject) {
renderObject.hasScrollBody = hasScrollBody;
renderObject.fillOverscroll = fillOverscroll;
}

RenderSliverFillRemaining — the RenderSliver part

double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;

Reminder:

viewportMainAxisExtent size of the viewport

precedingScrollExtent how far is the Sliver in the scroll view

The value is negative when Sliver is not yet visible and positive if it’s within the viewport.

double maxExtent = constraints.remainingPaintExtent — math.min(constraints.overlap, 0.0);

Reminder:

remainingPaintExtent starts with 0 until a first pixel of the sliver is visible, then goes up to viewport size.

overlap how much the previous sliver covers the next sliver.

This is the maximum size of how big can be our Sliver — it cannot be bigger than the viewport

In the case of iOS over-scroll, it can grow to be bigger than the extent

if (hasScrollBody) {
extent = maxExtent;
if (child != null) {
child.layout(
constraints.asBoxConstraints(
minExtent: extent,
maxExtent: extent,
),
parentUsesSize: true,
);
}
}

When the content of the sliver is scrollable, then we let the Sliver to take all the remaining space in the viewport and let the child handle the layout.

switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.getMaxIntrinsicWidth(constraints.crossAxisExtent);
break;
case Axis.vertical:
childExtent = child.getMaxIntrinsicHeight(constraints.crossAxisExtent);
break;
}

Reminder:

crossAxisExtent in case the scrollview’s direction is vertical this is the width of the viewport. When the scrollview’s direction is horizontal then it is its height.

In case that the content of the Sliver is not scrollable, we need to calculate the size of the Sliver by the content.

At this point we do not need to layout the child yet, therefore we can use intrinsic size.

if (constraints.precedingScrollExtent >   constraints.viewportMainAxisExtent ||
childExtent > extent) {
extent = childExtent;
}

Reminder:

precedingScrollExtent how far is the sliver in the scrollview.

viewportMainAxisExtent size of the viewport.

Checks if the Sliver is outside of the visible viewport OR if the Sliver’s size is smaller than it’s child’s size. If that is the case, it changes its size to the size of the child.

if (maxExtent < extent) {
maxExtent = extent;
}

We should not allow for the maximum size of the Sliver to be smaller than the minimum size.

if ((fillOverscroll ? maxExtent : extent) > childExtent) {
child.layout(
constraints.asBoxConstraints(
minExtent: extent,
maxExtent: fillOverscroll ? maxExtent : extent,
),
parentUsesSize: true,
);
}

When the fillOverscroll is enabled, allow the Sliver’s content to grow to enable iOS-like overscroll behaviour.

final double paintedChildSize = calculatePaintOffset(
constraints,
from: 0,
to: extent,
);

Calculate the size of Sliver when painting — this value will be used as paintExtent.

geometry = SliverGeometry(
scrollExtent: hasScrollBody ? constraints.viewportMainAxisExtent : extent,
paintExtent: paintedChildSize,
maxPaintExtent: paintedChildSize,
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);

The interesting part here is scrollExtent — if the Sliver has scrolling content, then the Sliver is as big as the viewport.

--

--

Tomek Polański
Flutter Community

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