Correct scroll notifications for NestedScrollView (#96482)
diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart
index fb994e4..a44118b 100644
--- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart
@@ -890,6 +890,9 @@
}
void pointerScroll(double delta) {
+ // If an update is made to pointer scrolling here, consider if the same
+ // (or similar) change should be made in
+ // ScrollPositionWithSingleContext.pointerScroll.
assert(delta != 0.0);
goIdle();
@@ -897,12 +900,15 @@
delta < 0.0 ? ScrollDirection.forward : ScrollDirection.reverse,
);
- // Set the isScrollingNotifier. Even if only one position actually receives
+ // Handle notifications. Even if only one position actually receives
// the delta, the NestedScrollView's intention is to treat multiple
// ScrollPositions as one.
_outerPosition!.isScrollingNotifier.value = true;
- for (final _NestedScrollPosition position in _innerPositions)
+ _outerPosition!.didStartScroll();
+ for (final _NestedScrollPosition position in _innerPositions) {
position.isScrollingNotifier.value = true;
+ position.didStartScroll();
+ }
if (_innerPositions.isEmpty) {
// Does not enter overscroll.
@@ -950,6 +956,11 @@
_outerPosition!.applyClampedPointerSignalUpdate(outerDelta);
}
}
+
+ _outerPosition!.didEndScroll();
+ for (final _NestedScrollPosition position in _innerPositions) {
+ position.didEndScroll();
+ }
goBallistic(0.0);
}
diff --git a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart
index 09dea8a..ab25d87 100644
--- a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart
+++ b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart
@@ -205,6 +205,9 @@
@override
void pointerScroll(double delta) {
+ // If an update is made to pointer scrolling here, consider if the same
+ // (or similar) change should be made in
+ // _NestedScrollCoordinator.pointerScroll.
assert(delta != 0.0);
final double targetPixels =
diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart
index da54000..8bf26e4 100644
--- a/packages/flutter/test/widgets/nested_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart
@@ -2491,6 +2491,68 @@
await tester.fling(find.text('Item 25'), const Offset(0.0, -50.0), 4000.0);
await tester.pumpAndSettle();
});
+
+ testWidgets('NestedScrollViewCoordinator.pointerScroll dispatches correct scroll notifications', (WidgetTester tester) async {
+ int scrollEnded = 0;
+ int scrollStarted = 0;
+ bool isScrolled = false;
+
+ await tester.pumpWidget(MaterialApp(
+ home: NotificationListener<ScrollNotification>(
+ onNotification: (ScrollNotification notification) {
+ if (notification is ScrollStartNotification) {
+ scrollStarted += 1;
+ } else if (notification is ScrollEndNotification) {
+ scrollEnded += 1;
+ }
+ return false;
+ },
+ child: Scaffold(
+ body: NestedScrollView(
+ headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+ isScrolled = innerBoxIsScrolled;
+ return <Widget>[
+ const SliverAppBar(
+ expandedHeight: 250.0,
+ ),
+ ];
+ },
+ body: CustomScrollView(
+ physics: const BouncingScrollPhysics(),
+ slivers: <Widget>[
+ SliverPadding(
+ padding: const EdgeInsets.all(8.0),
+ sliver: SliverFixedExtentList(
+ itemExtent: 48.0,
+ delegate: SliverChildBuilderDelegate(
+ (BuildContext context, int index) {
+ return ListTile(
+ title: Text('Item $index'),
+ );
+ },
+ childCount: 30,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ));
+
+ final Offset scrollEventLocation = tester.getCenter(find.byType(NestedScrollView));
+ final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
+ // Create a hover event so that |testPointer| has a location when generating the scroll.
+ testPointer.hover(scrollEventLocation);
+ await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 300.0)));
+ await tester.pumpAndSettle();
+
+ expect(isScrolled, isTrue);
+ // There should have been a notification for each nested position (2).
+ expect(scrollStarted, 2);
+ expect(scrollEnded, 2);
+ });
}
class TestHeader extends SliverPersistentHeaderDelegate {