Fix leading overscroll for RenderShrinkWrappingViewport (#87143)
diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart
index 1be3990..b7a8d6c 100644
--- a/packages/flutter/lib/src/rendering/viewport.dart
+++ b/packages/flutter/lib/src/rendering/viewport.dart
@@ -1924,7 +1924,7 @@
child: firstChild,
scrollOffset: math.max(0.0, correctedOffset),
overlap: math.min(0.0, correctedOffset),
- layoutOffset: 0.0,
+ layoutOffset: math.max(0.0, -correctedOffset),
remainingPaintExtent: mainAxisExtent,
mainAxisExtent: mainAxisExtent,
crossAxisExtent: crossAxisExtent,
diff --git a/packages/flutter/test/rendering/viewport_test.dart b/packages/flutter/test/rendering/viewport_test.dart
index 4520de3..91cb528 100644
--- a/packages/flutter/test/rendering/viewport_test.dart
+++ b/packages/flutter/test/rendering/viewport_test.dart
@@ -1843,6 +1843,188 @@
});
});
+ Widget _buildShrinkWrap({
+ ScrollController? controller,
+ Axis scrollDirection = Axis.vertical,
+ ScrollPhysics? physics,
+ }) {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: ListView.builder(
+ controller: controller,
+ physics: physics,
+ scrollDirection: scrollDirection,
+ shrinkWrap: true,
+ itemBuilder: (BuildContext context, int index) => SizedBox(height: 50, width: 50, child: Text('Item $index')),
+ itemCount: 20,
+ itemExtent: 50,
+ ),
+ ),
+ );
+ }
+
+ testWidgets('Shrinkwrap allows overscrolling on default platforms - vertical', (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/10949
+ // Scrollables should overscroll by default on iOS and macOS
+ final ScrollController controller = ScrollController();
+ await tester.pumpWidget(
+ _buildShrinkWrap(controller: controller),
+ );
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+ // Check overscroll at both ends
+ // Start
+ TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(0, 25));
+ await tester.pump();
+ expect(controller.offset, -25.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dy, 25.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+
+ // End
+ final double maxExtent = controller.position.maxScrollExtent;
+ controller.jumpTo(controller.position.maxScrollExtent);
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+
+ overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(0, -25));
+ await tester.pump();
+ expect(controller.offset, greaterThan(maxExtent));
+ expect(tester.getBottomLeft(find.text('Item 19')).dy, 375.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+ }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
+
+ testWidgets('Shrinkwrap allows overscrolling on default platforms - horizontal', (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/10949
+ // Scrollables should overscroll by default on iOS and macOS
+ final ScrollController controller = ScrollController();
+ await tester.pumpWidget(
+ _buildShrinkWrap(controller: controller, scrollDirection: Axis.horizontal),
+ );
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+ // Check overscroll at both ends
+ // Start
+ TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(25, 0));
+ await tester.pump();
+ expect(controller.offset, -25.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dx, 25.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+
+ // End
+ final double maxExtent = controller.position.maxScrollExtent;
+ controller.jumpTo(controller.position.maxScrollExtent);
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+
+ overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(-25, 0));
+ await tester.pump();
+ expect(controller.offset, greaterThan(maxExtent));
+ expect(tester.getTopRight(find.text('Item 19')).dx, 375.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+ }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
+
+ testWidgets('Shrinkwrap allows overscrolling per physics - vertical', (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/10949
+ // Scrollables should overscroll when the scroll physics allow
+ final ScrollController controller = ScrollController();
+ await tester.pumpWidget(
+ _buildShrinkWrap(controller: controller, physics: const BouncingScrollPhysics()),
+ );
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+ // Check overscroll at both ends
+ // Start
+ TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(0, 25));
+ await tester.pump();
+ expect(controller.offset, -25.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dy, 25.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+
+ // End
+ final double maxExtent = controller.position.maxScrollExtent;
+ controller.jumpTo(controller.position.maxScrollExtent);
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+
+ overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(0, -25));
+ await tester.pump();
+ expect(controller.offset, greaterThan(maxExtent));
+ expect(tester.getBottomLeft(find.text('Item 19')).dy, 375.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+ });
+
+ testWidgets('Shrinkwrap allows overscrolling per physics - horizontal', (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/10949
+ // Scrollables should overscroll when the scroll physics allow
+ final ScrollController controller = ScrollController();
+ await tester.pumpWidget(
+ _buildShrinkWrap(
+ controller: controller,
+ scrollDirection: Axis.horizontal,
+ physics: const BouncingScrollPhysics(),
+ ),
+ );
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+ // Check overscroll at both ends
+ // Start
+ TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(25, 0));
+ await tester.pump();
+ expect(controller.offset, -25.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dx, 25.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, 0.0);
+ expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+
+ // End
+ final double maxExtent = controller.position.maxScrollExtent;
+ controller.jumpTo(controller.position.maxScrollExtent);
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+
+ overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+ await overscrollGesture.moveBy(const Offset(-25, 0));
+ await tester.pump();
+ expect(controller.offset, greaterThan(maxExtent));
+ expect(tester.getTopRight(find.text('Item 19')).dx, 375.0);
+ await overscrollGesture.up();
+ await tester.pumpAndSettle();
+ expect(controller.offset, maxExtent);
+ expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+ });
+
testWidgets('Handles infinite constraints when TargetPlatform is iOS or macOS', (WidgetTester tester) async {
// regression test for https://github.com/flutter/flutter/issues/45866
await tester.pumpWidget(