How Slivers are made: SliverFillRemaining
SliverFillRemaining
is fairly simple yet useful — it fills the remaining scrollable space with a widget of your choice.
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 theSliver
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.
Now you know how to write SliverFillRemaining
.