Handle ListView item size changes that cause underflow (#9586)

This commit is contained in:
Hans Muller 2017-04-26 10:44:35 -07:00 committed by GitHub
parent b7ec82014d
commit 3c3b003f8b
2 changed files with 105 additions and 1 deletions

View file

@ -97,6 +97,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
earliestScrollOffset = childScrollOffset(earliestUsefulChild)) {
// We have to add children before the earliestUsefulChild.
earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
if (earliestUsefulChild == null) {
// We ran out of children before reaching the scroll offset.
// We must inform our parent that this sliver cannot fulfill
@ -108,8 +109,33 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
childParentData.layoutOffset = 0.0;
return;
}
final double firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild);
if (firstChildScrollOffset < 0.0) {
// The first child doesn't fit within the viewport (underflow) and
// there may be additional children above it. Find the real first child
// and then correct the scroll position so that there's room for all and
// so that the trailing edge of the original firstChild appears where it
// was before the scroll offset correction.
// TODO(hansmuller): do this work incrementally, instead of all at once,
// i.e. find a way to avoid visiting ALL of the children whose offset
// is < 0 before returning for the scroll correction.
double correction = 0.0;
while (earliestUsefulChild != null) {
assert(firstChild == earliestUsefulChild);
correction += paintExtentOf(firstChild);
earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
}
geometry = new SliverGeometry(
scrollOffsetCorrection: correction - earliestScrollOffset,
);
final SliverMultiBoxAdaptorParentData childParentData = firstChild.parentData;
childParentData.layoutOffset = 0.0;
return;
}
final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData;
childParentData.layoutOffset = earliestScrollOffset - paintExtentOf(firstChild);
childParentData.layoutOffset = firstChildScrollOffset;
assert(earliestUsefulChild == firstChild);
leadingChildWithLayout = earliestUsefulChild;
trailingChildWithLayout ??= earliestUsefulChild;

View file

@ -53,4 +53,82 @@ void main() {
expect(tester.getTopLeft(find.text('2')).dy, equals(200.0));
});
testWidgets('ListView can handle inserts at 0', (WidgetTester tester) async {
final ScrollController controller = new ScrollController();
await tester.pumpWidget(new ListView(
controller: controller,
children: <Widget>[
new Container(height: 400.0, child: const Text('0')),
new Container(height: 400.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
],
));
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
final Finder findItemA = find.descendant(of: find.byType(Container), matching: find.text('A'));
final Finder findItemB = find.descendant(of: find.byType(Container), matching: find.text('B'));
await tester.pumpWidget(new ListView(
controller: controller,
children: <Widget>[
new Container(height: 10.0, child: const Text('A')),
new Container(height: 10.0, child: const Text('B')),
new Container(height: 400.0, child: const Text('0')),
new Container(height: 400.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
],
));
expect(find.text('A'), findsOneWidget);
expect(find.text('B'), findsOneWidget);
expect(tester.getTopLeft(findItemA).dy, 0.0);
expect(tester.getBottomRight(findItemA).dy, 10.0);
expect(tester.getTopLeft(findItemB).dy, 10.0);
expect(tester.getBottomRight(findItemB).dy, 20.0);
controller.jumpTo(1200.0);
await tester.pump();
expect(find.text('A'), findsNothing);
expect(find.text('B'), findsNothing);
await tester.pumpWidget(new ListView(
controller: controller,
children: <Widget>[
new Container(height: 200.0, child: const Text('A')),
new Container(height: 200.0, child: const Text('B')),
new Container(height: 400.0, child: const Text('0')),
new Container(height: 400.0, child: const Text('1')),
new Container(height: 400.0, child: const Text('2')),
new Container(height: 400.0, child: const Text('3')),
],
));
expect(find.text('A'), findsNothing);
expect(find.text('B'), findsNothing);
// Scrolling to 0 causes items A and B to underflow (extend below
// scrollOffset 0) because their heights have grown from 10 - 200.
// RenderSliver list corrects the scroll offset in this case. Only item
// B will become visible and item B's bottom edge will still appear
// where it was when its height was 10.0.
controller.jumpTo(0.0);
await tester.pump();
expect(find.text('B'), findsOneWidget);
expect(controller.offset, greaterThan(0.0)); // RenderSliverList corrected the offset.
expect(tester.getTopLeft(findItemB).dy, -180.0);
expect(tester.getBottomRight(findItemB).dy, 20.0);
controller.jumpTo(0.0);
await tester.pump();
expect(find.text('A'), findsOneWidget);
expect(find.text('B'), findsOneWidget);
expect(tester.getTopLeft(findItemA).dy, 0.0);
expect(tester.getBottomRight(findItemA).dy, 200.0);
expect(tester.getTopLeft(findItemB).dy, 200.0);
expect(tester.getBottomRight(findItemB).dy, 400.0);
});
}