[SingleChildScrollView] Correct the offset pixels if it is out of range during layout (#136239)
Fixes https://github.com/flutter/flutter/issues/105733
FIxes https://github.com/flutter/flutter/issues/135865
The above two issues will not occur on the `ListView` widget, so after analyzing their differences, it was found that the `SingleChildScrollView` pixels were not corrected according to the content dimensions during layout.
### ListView correct the pixels codes
5dfd78c2e3/packages/flutter/lib/src/rendering/viewport.dart (L1462-L1465)
We should correct the pixels to prevent the physics ballistic simulations.
This change makes them have the same behavior.
This commit is contained in:
parent
22b0a62a0c
commit
3f722c1442
@ -495,6 +495,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
|||||||
size = constraints.constrain(child!.size);
|
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.applyViewportDimension(_viewportExtent);
|
||||||
offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
|
offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
|
||||||
}
|
}
|
||||||
|
@ -92,19 +92,40 @@ void main() {
|
|||||||
expect(scrollable.position.pixels, equals(50.0));
|
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<void> testOutOfBounds(ScrollPhysics physics, double initialOffset, double expectedOffset) async {
|
Future<void> testOutOfBounds(ScrollPhysics physics, double initialOffset, double expectedOffset) async {
|
||||||
final ScrollController scrollController = ScrollController(initialScrollOffset: initialOffset);
|
final ScrollController scrollController = ScrollController(initialScrollOffset: initialOffset);
|
||||||
addTearDown(scrollController.dispose);
|
addTearDown(scrollController.dispose);
|
||||||
await tester.pumpWidget(buildFrame(physics, scrollController: scrollController));
|
await tester.pumpWidget(buildFrame(physics, scrollController: scrollController));
|
||||||
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
|
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
|
||||||
|
|
||||||
expect(scrollable.position.pixels, equals(initialOffset));
|
// The initialScrollOffset will be corrected during the first frame.
|
||||||
await tester.pump(const Duration(seconds: 1)); // Allow overscroll to settle
|
|
||||||
expect(scrollable.position.pixels, equals(expectedOffset));
|
expect(scrollable.position.pixels, equals(expectedOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
await testOutOfBounds(const ClampingScrollPhysics(), -400.0, 0.0);
|
await testOutOfBounds(const ClampingScrollPhysics(), -400.0, 0.0);
|
||||||
await testOutOfBounds(const ClampingScrollPhysics(), 800.0, 50.0);
|
await testOutOfBounds(const ClampingScrollPhysics(), 800.0, 50.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgetsWithLeakTracking('ClampingScrollPhysics handles out of bounds ScrollPosition - jumpTo', (WidgetTester tester) async {
|
||||||
|
Future<void> 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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,17 @@ void main() {
|
|||||||
}
|
}
|
||||||
await tester.pumpWidget(buildFrame(1200.0));
|
await tester.pumpWidget(buildFrame(1200.0));
|
||||||
expect(events.length, 1);
|
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();
|
events.clear();
|
||||||
await tester.pumpWidget(buildFrame(1000.0));
|
await tester.pumpWidget(buildFrame(1000.0));
|
||||||
// Change the content dimensions will trigger a new event.
|
// Change the content dimensions will trigger a new event.
|
||||||
expect(events.length, 1);
|
expect(events.length, 1);
|
||||||
ScrollMetricsNotification event = events[0] as ScrollMetricsNotification;
|
event = events[0] as ScrollMetricsNotification;
|
||||||
expect(event.metrics.extentBefore, 0.0);
|
expect(event.metrics.extentBefore, 0.0);
|
||||||
expect(event.metrics.extentInside, 600.0);
|
expect(event.metrics.extentInside, 600.0);
|
||||||
expect(event.metrics.extentAfter, 400.0);
|
expect(event.metrics.extentAfter, 400.0);
|
||||||
@ -36,24 +41,27 @@ void main() {
|
|||||||
|
|
||||||
events.clear();
|
events.clear();
|
||||||
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
|
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
|
||||||
expect(events.length, 1);
|
await tester.pump(const Duration(seconds: 1));
|
||||||
// user scroll do not trigger the ScrollContentMetricsNotification.
|
|
||||||
expect(events[0] is ScrollStartNotification, true);
|
|
||||||
|
|
||||||
events.clear();
|
|
||||||
await gesture.moveBy(const Offset(-10.0, -10.0));
|
await gesture.moveBy(const Offset(-10.0, -10.0));
|
||||||
expect(events.length, 2);
|
await tester.pump(const Duration(seconds: 1));
|
||||||
// User scroll do not trigger the ScrollContentMetricsNotification.
|
await gesture.up();
|
||||||
expect(events[0] is UserScrollNotification, true);
|
await tester.pump(const Duration(seconds: 1));
|
||||||
expect(events[1] is ScrollUpdateNotification, true);
|
|
||||||
|
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();
|
events.clear();
|
||||||
// Change the content dimensions again.
|
// Change the content dimensions again.
|
||||||
await tester.pumpWidget(buildFrame(500.0));
|
await tester.pumpWidget(buildFrame(500.0));
|
||||||
expect(events.length, 1);
|
expect(events.length, 1);
|
||||||
event = events[0] as ScrollMetricsNotification;
|
event = events[0] as ScrollMetricsNotification;
|
||||||
expect(event.metrics.extentBefore, 10.0);
|
expect(event.metrics.extentBefore, 0.0);
|
||||||
expect(event.metrics.extentInside, 590.0);
|
expect(event.metrics.extentInside, 600.0);
|
||||||
expect(event.metrics.extentAfter, 0.0);
|
expect(event.metrics.extentAfter, 0.0);
|
||||||
expect(event.metrics.extentTotal, 600.0);
|
expect(event.metrics.extentTotal, 600.0);
|
||||||
|
|
||||||
|
@ -1039,7 +1039,7 @@ void main() {
|
|||||||
|
|
||||||
// Make the outer constraints larger that the scrollable widget is no longer able to scroll.
|
// Make the outer constraints larger that the scrollable widget is no longer able to scroll.
|
||||||
await tester.pumpWidget(build(300.0));
|
await tester.pumpWidget(build(300.0));
|
||||||
expect(controller.position.pixels, 100.0);
|
expect(controller.position.pixels, 0.0);
|
||||||
expect(controller.position.maxScrollExtent, 0.0);
|
expect(controller.position.maxScrollExtent, 0.0);
|
||||||
|
|
||||||
// Hover over the scroll view and create a zero offset pointer scroll.
|
// Hover over the scroll view and create a zero offset pointer scroll.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user