You will need to create a custom RenderBox for this. As there's no widgets which supports this out of the box.
SliverFillRemaining
comes pretty close. But it's sizing/scrolling behavior is different then what you'd expect. As, if present, will almost always make the Scrollable
... scrollable.
Instead, we can copy paste the sources of SliverFillRemaining
. And make some edits
class SliverFooter extends SingleChildRenderObjectWidget {
/// Creates a sliver that fills the remaining space in the viewport.
const SliverFooter({super.child});
@override
RenderSliverFooter createRenderObject(BuildContext context) =>
RenderSliverFooter();
}
class RenderSliverFooter extends RenderSliverSingleBoxAdapter {
@override
void performLayout() {
final extent =
constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
double childGrowthSize = .0; // added
if (child != null) {
// changed maxExtent from 'extent' to double.infinity
child!.layout(
constraints.asBoxConstraints(minExtent: extent),
parentUsesSize: true,
);
childGrowthSize = constraints.axis == Axis.vertical
? child!.size.height
: child!.size.width; // added
}
final paintedChildSize =
calculatePaintOffset(constraints, from: 0.0, to: extent);
assert(
paintedChildSize.isFinite,
'The calculated paintedChildSize '
'$paintedChildSize for child $child is not finite.');
assert(
paintedChildSize >= 0.0,
'The calculated paintedChildSize was '
'$paintedChildSize but is not greater than or equal to zero. '
'This can happen if the child is too big in which case it '
'should be sized down or if the SliverConstraints.scrollOffset '
'was not correct.');
geometry = SliverGeometry(
// used to be this : scrollExtent: constraints.viewportMainAxisExtent,
scrollExtent: math.max(extent, childGrowthSize),
paintExtent: paintedChildSize,
maxPaintExtent: paintedChildSize,
hasVisualOverflow: extent > constraints.remainingPaintExtent ||
constraints.scrollOffset > 0.0,
);
if (child != null) {
setChildParentData(child!, constraints, geometry!);
}
}
}
Here I changed one 3 things
- Unconstrained the child
maxExtent
. Because if there's no more screen-space available, that would enforce a height of 0 to the footer.
- changed
SliverGeometry
scrollExtent
from "full screen height" to "actual available size". So that it actually only fill the remaining visible space. Not fill the screen.
- added a minimum value to that same
scrollExtent
, equal to the actual footer height. So that if there's no more space remaining in the viewport, the children is simply added without any spacing around it.
We can now use it inside our CustomScrollView
as usual.
End result :
CustomScrollView(
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 42.0,
delegate: SliverChildBuilderDelegate((context, index) {
return SizedBox.expand(
child: Card(),
);
}, childCount: 42),
),
SliverFooter(
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 42.0,
color: Colors.red,
),
),
),
],
),