diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart index 59b4956f5f..c7c7a1b87e 100644 --- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart @@ -495,6 +495,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix size = constraints.constrain(child!.size); } + if (offset.hasPixels) { + if (offset.pixels > _maxScrollExtent) { + offset.correctBy(_maxScrollExtent - offset.pixels); + } else if (offset.pixels < _minScrollExtent) { + offset.correctBy(_minScrollExtent - offset.pixels); + } + } + offset.applyViewportDimension(_viewportExtent); offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent); } diff --git a/packages/flutter/test/widgets/clamp_overscrolls_test.dart b/packages/flutter/test/widgets/clamp_overscrolls_test.dart index 856648f0aa..f12862be85 100644 --- a/packages/flutter/test/widgets/clamp_overscrolls_test.dart +++ b/packages/flutter/test/widgets/clamp_overscrolls_test.dart @@ -92,19 +92,40 @@ void main() { expect(scrollable.position.pixels, equals(50.0)); }); - testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition', (WidgetTester tester) async { + testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition - initialScrollOffset', (WidgetTester tester) async { Future testOutOfBounds(ScrollPhysics physics, double initialOffset, double expectedOffset) async { final ScrollController scrollController = ScrollController(initialScrollOffset: initialOffset); addTearDown(scrollController.dispose); await tester.pumpWidget(buildFrame(physics, scrollController: scrollController)); final ScrollableState scrollable = tester.state(find.byType(Scrollable)); - expect(scrollable.position.pixels, equals(initialOffset)); - await tester.pump(const Duration(seconds: 1)); // Allow overscroll to settle + // The initialScrollOffset will be corrected during the first frame. expect(scrollable.position.pixels, equals(expectedOffset)); } await testOutOfBounds(const ClampingScrollPhysics(), -400.0, 0.0); await testOutOfBounds(const ClampingScrollPhysics(), 800.0, 50.0); }); + + testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition - jumpTo', (WidgetTester tester) async { + Future testOutOfBounds(ScrollPhysics physics, double targetOffset, double endingOffset) async { + final ScrollController scrollController = ScrollController(); + addTearDown(scrollController.dispose); + await tester.pumpWidget(buildFrame(physics, scrollController: scrollController)); + final ScrollableState scrollable = tester.state(find.byType(Scrollable)); + + expect(scrollable.position.pixels, equals(0.0)); + + scrollController.jumpTo(targetOffset); + await tester.pump(); + + expect(scrollable.position.pixels, equals(targetOffset)); + + await tester.pump(const Duration(seconds: 1)); // Allow overscroll animation to settle + expect(scrollable.position.pixels, equals(endingOffset)); + } + + await testOutOfBounds(const ClampingScrollPhysics(), -400.0, 0.0); + await testOutOfBounds(const ClampingScrollPhysics(), 800.0, 50.0); + }); } diff --git a/packages/flutter/test/widgets/scroll_notification_test.dart b/packages/flutter/test/widgets/scroll_notification_test.dart index d1b1c7f6d7..4b33d4d5d1 100644 --- a/packages/flutter/test/widgets/scroll_notification_test.dart +++ b/packages/flutter/test/widgets/scroll_notification_test.dart @@ -23,12 +23,17 @@ void main() { } await tester.pumpWidget(buildFrame(1200.0)); expect(events.length, 1); + ScrollMetricsNotification event = events[0] as ScrollMetricsNotification; + expect(event.metrics.extentBefore, 0.0); + expect(event.metrics.extentInside, 600.0); + expect(event.metrics.extentAfter, 600.0); + expect(event.metrics.extentTotal, 1200.0); events.clear(); await tester.pumpWidget(buildFrame(1000.0)); // Change the content dimensions will trigger a new event. expect(events.length, 1); - ScrollMetricsNotification event = events[0] as ScrollMetricsNotification; + event = events[0] as ScrollMetricsNotification; expect(event.metrics.extentBefore, 0.0); expect(event.metrics.extentInside, 600.0); expect(event.metrics.extentAfter, 400.0); @@ -36,24 +41,27 @@ void main() { events.clear(); final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0)); - expect(events.length, 1); - // user scroll do not trigger the ScrollContentMetricsNotification. - expect(events[0] is ScrollStartNotification, true); - - events.clear(); + await tester.pump(const Duration(seconds: 1)); await gesture.moveBy(const Offset(-10.0, -10.0)); - expect(events.length, 2); - // User scroll do not trigger the ScrollContentMetricsNotification. - expect(events[0] is UserScrollNotification, true); - expect(events[1] is ScrollUpdateNotification, true); + await tester.pump(const Duration(seconds: 1)); + await gesture.up(); + await tester.pump(const Duration(seconds: 1)); + + expect(events.length, 5); + // user scroll do not trigger the ScrollMetricsNotification. + expect(events[0] is ScrollStartNotification, true); + expect(events[1] is UserScrollNotification, true); + expect(events[2] is ScrollUpdateNotification, true); + expect(events[3] is ScrollEndNotification, true); + expect(events[4] is UserScrollNotification, true); events.clear(); // Change the content dimensions again. await tester.pumpWidget(buildFrame(500.0)); expect(events.length, 1); event = events[0] as ScrollMetricsNotification; - expect(event.metrics.extentBefore, 10.0); - expect(event.metrics.extentInside, 590.0); + expect(event.metrics.extentBefore, 0.0); + expect(event.metrics.extentInside, 600.0); expect(event.metrics.extentAfter, 0.0); expect(event.metrics.extentTotal, 600.0); diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index 14d6ba63ec..5d5d8f0826 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -1039,7 +1039,7 @@ void main() { // Make the outer constraints larger that the scrollable widget is no longer able to scroll. await tester.pumpWidget(build(300.0)); - expect(controller.position.pixels, 100.0); + expect(controller.position.pixels, 0.0); expect(controller.position.maxScrollExtent, 0.0); // Hover over the scroll view and create a zero offset pointer scroll.