Skip to content

Commit c3f4524

Browse files
authored
Reland [SingleChildScrollView] Correct the offset pixels if it is out of range during layout (#136871)
Reland flutter/flutter#136239 which was reverted by flutter/flutter#136744 Fixes flutter/flutter#105733 FIxes flutter/flutter#135865 Do not merge before the customer finish the migration. @Piinks
1 parent 98827bb commit c3f4524

File tree

4 files changed

+53
-16
lines changed

4 files changed

+53
-16
lines changed

packages/flutter/lib/src/widgets/single_child_scroll_view.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
495495
size = constraints.constrain(child!.size);
496496
}
497497

498+
if (offset.hasPixels) {
499+
if (offset.pixels > _maxScrollExtent) {
500+
offset.correctBy(_maxScrollExtent - offset.pixels);
501+
} else if (offset.pixels < _minScrollExtent) {
502+
offset.correctBy(_minScrollExtent - offset.pixels);
503+
}
504+
}
505+
498506
offset.applyViewportDimension(_viewportExtent);
499507
offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
500508
}

packages/flutter/test/widgets/clamp_overscrolls_test.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,40 @@ void main() {
9292
expect(scrollable.position.pixels, equals(50.0));
9393
});
9494

95-
testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition', (WidgetTester tester) async {
95+
testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition - initialScrollOffset', (WidgetTester tester) async {
9696
Future<void> testOutOfBounds(ScrollPhysics physics, double initialOffset, double expectedOffset) async {
9797
final ScrollController scrollController = ScrollController(initialScrollOffset: initialOffset);
9898
addTearDown(scrollController.dispose);
9999
await tester.pumpWidget(buildFrame(physics, scrollController: scrollController));
100100
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
101101

102-
expect(scrollable.position.pixels, equals(initialOffset));
103-
await tester.pump(const Duration(seconds: 1)); // Allow overscroll to settle
102+
// The initialScrollOffset will be corrected during the first frame.
104103
expect(scrollable.position.pixels, equals(expectedOffset));
105104
}
106105

107106
await testOutOfBounds(const ClampingScrollPhysics(), -400.0, 0.0);
108107
await testOutOfBounds(const ClampingScrollPhysics(), 800.0, 50.0);
109108
});
109+
110+
testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition - jumpTo', (WidgetTester tester) async {
111+
Future<void> testOutOfBounds(ScrollPhysics physics, double targetOffset, double endingOffset) async {
112+
final ScrollController scrollController = ScrollController();
113+
addTearDown(scrollController.dispose);
114+
await tester.pumpWidget(buildFrame(physics, scrollController: scrollController));
115+
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
116+
117+
expect(scrollable.position.pixels, equals(0.0));
118+
119+
scrollController.jumpTo(targetOffset);
120+
await tester.pump();
121+
122+
expect(scrollable.position.pixels, equals(targetOffset));
123+
124+
await tester.pump(const Duration(seconds: 1)); // Allow overscroll animation to settle
125+
expect(scrollable.position.pixels, equals(endingOffset));
126+
}
127+
128+
await testOutOfBounds(const ClampingScrollPhysics(), -400.0, 0.0);
129+
await testOutOfBounds(const ClampingScrollPhysics(), 800.0, 50.0);
130+
});
110131
}

packages/flutter/test/widgets/scroll_notification_test.dart

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,45 @@ void main() {
2323
}
2424
await tester.pumpWidget(buildFrame(1200.0));
2525
expect(events.length, 1);
26+
ScrollMetricsNotification event = events[0] as ScrollMetricsNotification;
27+
expect(event.metrics.extentBefore, 0.0);
28+
expect(event.metrics.extentInside, 600.0);
29+
expect(event.metrics.extentAfter, 600.0);
30+
expect(event.metrics.extentTotal, 1200.0);
2631

2732
events.clear();
2833
await tester.pumpWidget(buildFrame(1000.0));
2934
// Change the content dimensions will trigger a new event.
3035
expect(events.length, 1);
31-
ScrollMetricsNotification event = events[0] as ScrollMetricsNotification;
36+
event = events[0] as ScrollMetricsNotification;
3237
expect(event.metrics.extentBefore, 0.0);
3338
expect(event.metrics.extentInside, 600.0);
3439
expect(event.metrics.extentAfter, 400.0);
3540
expect(event.metrics.extentTotal, 1000.0);
3641

3742
events.clear();
3843
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
39-
expect(events.length, 1);
40-
// user scroll do not trigger the ScrollContentMetricsNotification.
41-
expect(events[0] is ScrollStartNotification, true);
42-
43-
events.clear();
44+
await tester.pump(const Duration(seconds: 1));
4445
await gesture.moveBy(const Offset(-10.0, -10.0));
45-
expect(events.length, 2);
46-
// User scroll do not trigger the ScrollContentMetricsNotification.
47-
expect(events[0] is UserScrollNotification, true);
48-
expect(events[1] is ScrollUpdateNotification, true);
46+
await tester.pump(const Duration(seconds: 1));
47+
await gesture.up();
48+
await tester.pump(const Duration(seconds: 1));
49+
50+
expect(events.length, 5);
51+
// user scroll do not trigger the ScrollMetricsNotification.
52+
expect(events[0] is ScrollStartNotification, true);
53+
expect(events[1] is UserScrollNotification, true);
54+
expect(events[2] is ScrollUpdateNotification, true);
55+
expect(events[3] is ScrollEndNotification, true);
56+
expect(events[4] is UserScrollNotification, true);
4957

5058
events.clear();
5159
// Change the content dimensions again.
5260
await tester.pumpWidget(buildFrame(500.0));
5361
expect(events.length, 1);
5462
event = events[0] as ScrollMetricsNotification;
55-
expect(event.metrics.extentBefore, 10.0);
56-
expect(event.metrics.extentInside, 590.0);
63+
expect(event.metrics.extentBefore, 0.0);
64+
expect(event.metrics.extentInside, 600.0);
5765
expect(event.metrics.extentAfter, 0.0);
5866
expect(event.metrics.extentTotal, 600.0);
5967

packages/flutter/test/widgets/scrollable_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,7 @@ void main() {
10391039

10401040
// Make the outer constraints larger that the scrollable widget is no longer able to scroll.
10411041
await tester.pumpWidget(build(300.0));
1042-
expect(controller.position.pixels, 100.0);
1042+
expect(controller.position.pixels, 0.0);
10431043
expect(controller.position.maxScrollExtent, 0.0);
10441044

10451045
// Hover over the scroll view and create a zero offset pointer scroll.

0 commit comments

Comments
 (0)