I have a PageView
and I want to use it to show an indefinite amount of widgets in columns. I'm trying to add widgets to a column until it would overflow, and then build the next page beginning with the widget that would have overflowed.
So far, this is what I've hacked together in an effort to get something working:
class OverflowSliverChildDelegate extends SliverChildDelegate {
OverflowSliverChildDelegate({
@required this.itemBuilder,
}) : assert(itemBuilder != null),
super();
/// Used to build individual items to slot into pages.
final IndexedWidgetBuilder itemBuilder;
@override
Widget build(BuildContext context, int index) {
return LayoutBuilder(
builder: (context, constraints) {
// todo: handle this better
if (remainingHeight <= 0) throw Error();
var itemIndex = 0;
return PageView.builder(
itemBuilder: (context, pageIndex) {
final remainingHeight = constraints.maxHeight;
return Column(
// todo: move this generator to a separate method
children: () sync* {
// Build widgets until we run out of vertical space.
while (remainingHeight > 0) {
// todo: layout widget and get its height
final widget = itemBuilder(context, itemIndex++);
// remainingHeight -= widget.height
yield widget;
}
}(),
);
},
);
},
);
}
}
If I was only concerned with text, I could use a TextPainter
to layout the text and get the height. Is there a way to do this for arbitrary widgets (which may or may not have children of their own)?
Update
After looking into the Offstage
widget, I tried this but Flutter is asserting that I don't call the size
getter from outside the object. I felt so close to a solution too.
while (remainingHeight > 0) {
final widget = itemBuilder(context, itemIndex++);
final renderObj = ConstrainedBox(
child: widget,
constraints: constraints.copyWith(
maxHeight: remainingHeight,
),
).createRenderObject(context);
renderObj.layout(constraints);
remainingHeight -= renderObj.size.height;
if (remainingHeight > 0) {
yield widget;
}
}
I ended up digging into Flutter's source code for the sliver list (essentially the backend for the ListView
widget) and I was able to figure out how it decided when it had enough children to fill the viewport and the "cache extent".
I've started a fresh Flutter project in order to try different approaches but I've cleaned it up a little and published it to the pub.dev: https://pub.dev/packages/overflow_page_view
The result is a PageView
which builds a CustomScrollView
for each page. I've straight-up copied the code for the RenderSliver
stuff from the Flutter source and changed the parts where it lays out its children. Ideally, I would have liked to have found a way to pass custom SliverConstraints
to a ListView
or other sliver widget.
As for answering the question "How can I detect if a widget will overflow its constraints before it is rendered?":
I copied the code for RenderSliverList
into my own class (NoCacheRenderSliverList
) and began tweaking things. It should be noted that I used NeverScrollableScrollPhysics
so that I didn't have to worry about scrolling (because I don't need it).
First off, at the top of the performLayout
method, I changed the targetEndScrollOffset
to be the remaining paint extent and not anything to do with the scroll offset or the cache extent. Only concerned with visible area here:
final double targetEndScrollOffset = this.constraints.remainingPaintExtent;
Next, I need to change the nested bool advance()
method to return false when the bottom of the child goes beyond the edge of the viewport:
assert(child != null);
final SliverMultiBoxAdaptorParentData childParentData =
child.parentData as SliverMultiBoxAdaptorParentData;
childParentData.layoutOffset = endScrollOffset;
assert(childParentData.index == index);
// In the original source, this is assigned directly to endScrollOffset
final tmp = childScrollOffset(child) + paintExtentOf(child);
if (tmp > targetEndScrollOffset) {
return false;
}
endScrollOffset = tmp;
return true;
With the above code, we'll still end up showing a single child that extends beyond the viewport. My last change is to ensure that this child is garbage collected:
// Finally count up all the remaining children and label them as garbage.
if (child != null) {
// By commenting out this line, we ensure the final child is garbage collected.
// child = childAfter(child);
while (child != null) {
trailingGarbage += 1;
child = childAfter(child);
}
}
And that's all there is to it. If anyone has any improvement or needs clarification I'm happy to update this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With