From 36a7e3f0efa3a1ede4a239ac6da288f9212d97c0 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Tue, 3 Sep 2019 10:27:37 -0700 Subject: [PATCH] fix sliverfixedextent with sliverchildbuilderdelegate does not correct calculate max scroll extent (#39142) --- .../rendering/sliver_fixed_extent_list.dart | 36 +++++++++-- .../flutter/test/widgets/slivers_test.dart | 64 +++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart index 131cf382b03..4916cf615a8 100644 --- a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart +++ b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart @@ -195,7 +195,24 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda if (firstChild == null) { if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) { // There are either no children, or we are past the end of all our children. - final double max = computeMaxScrollOffset(constraints, itemExtent); + // If it is the later, we will need to find the first available child. + double max; + if (childManager.childCount != null) { + max = computeMaxScrollOffset(constraints, itemExtent); + } else if (firstIndex <= 0) { + max = 0.0; + } else { + // We will have to find it manually. + int possibleFirstIndex = firstIndex - 1; + while (possibleFirstIndex > 0 && + !addInitialChild( + index: possibleFirstIndex, + layoutOffset: indexToLayoutOffset(itemExtent, possibleFirstIndex) + ) + ) + possibleFirstIndex -= 1; + max = possibleFirstIndex * itemExtent; + } geometry = SliverGeometry( scrollExtent: max, maxPaintExtent: max, @@ -229,12 +246,14 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda trailingChildWithLayout = firstChild; } + double estimatedMaxScrollOffset = double.infinity; for (int index = indexOf(trailingChildWithLayout) + 1; targetLastIndex == null || index <= targetLastIndex; ++index) { RenderBox child = childAfter(trailingChildWithLayout); if (child == null || indexOf(child) != index) { child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout); if (child == null) { // We have run out of children. + estimatedMaxScrollOffset = index * itemExtent; break; } } else { @@ -256,12 +275,15 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda assert(indexOf(firstChild) == firstIndex); assert(targetLastIndex == null || lastIndex <= targetLastIndex); - final double estimatedMaxScrollOffset = estimateMaxScrollOffset( - constraints, - firstIndex: firstIndex, - lastIndex: lastIndex, - leadingScrollOffset: leadingScrollOffset, - trailingScrollOffset: trailingScrollOffset, + estimatedMaxScrollOffset = math.min( + estimatedMaxScrollOffset, + estimateMaxScrollOffset( + constraints, + firstIndex: firstIndex, + lastIndex: lastIndex, + leadingScrollOffset: leadingScrollOffset, + trailingScrollOffset: trailingScrollOffset, + ) ); final double paintExtent = calculatePaintOffset( diff --git a/packages/flutter/test/widgets/slivers_test.dart b/packages/flutter/test/widgets/slivers_test.dart index 44a942bf8d7..0dd72150ba4 100644 --- a/packages/flutter/test/widgets/slivers_test.dart +++ b/packages/flutter/test/widgets/slivers_test.dart @@ -350,6 +350,70 @@ void main() { expect(tester.takeException(), 'builder'); ErrorWidget.builder = oldBuilder; }); + + testWidgets('SliverFixedExtentList with SliverChildBuilderDelegate auto-correct scroll offset - super fast', (WidgetTester tester) async { + final ScrollController controller = ScrollController(initialScrollOffset: 600); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: CustomScrollView( + controller: controller, + cacheExtent: 0, + slivers: [ + SliverFixedExtentList( + itemExtent: 200, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index <= 6) { + return Center(child: Text('Page $index')); + } + return null; + }, + ), + ) + ], + ), + ) + ); + await tester.drag(find.text('Page 5'), const Offset(0, -1000)); + // Controller will be temporarily over-scrolled. + expect(controller.offset, 1600.0); + await tester.pumpAndSettle(); + // It will be corrected after a auto scroll animation. + expect(controller.offset, 800.0); + }); + + testWidgets('SliverFixedExtentList with SliverChildBuilderDelegate auto-correct scroll offset - reasonable', (WidgetTester tester) async { + final ScrollController controller = ScrollController(initialScrollOffset: 600); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: CustomScrollView( + controller: controller, + cacheExtent: 0, + slivers: [ + SliverFixedExtentList( + itemExtent: 200, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index <= 6) { + return Center(child: Text('Page $index')); + } + return null; + }, + ), + ) + ], + ), + ) + ); + await tester.drag(find.text('Page 5'), const Offset(0, -210)); + // Controller will be temporarily over-scrolled. + expect(controller.offset, 810.0); + await tester.pumpAndSettle(); + // It will be corrected after a auto scroll animation. + expect(controller.offset, 800.0); + }); } bool isRight(Offset a, Offset b) => b.dx > a.dx;