diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 83e6c3b711f..3cfba40a9a1 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -909,21 +909,31 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont if (_innerPositions.isEmpty) { _outerPosition.applyFullDragUpdate(delta); } else if (delta < 0.0) { - // dragging "up" - // TODO(ianh): prioritize first getting rid of overscroll, and then the - // outer view, so that the app bar will scroll out of the way asap. - // Right now we ignore overscroll. This works fine on Android but looks - // weird on iOS if you fling down then up. The problem is it's not at all - // clear what this should do when you have multiple inner positions at - // different levels of overscroll. - final double innerDelta = _outerPosition.applyClampedDragUpdate(delta); - if (innerDelta != 0.0) { - for (final _NestedScrollPosition position in _innerPositions) - position.applyFullDragUpdate(innerDelta); + // Dragging "up" + // Prioritize getting rid of any inner overscroll, and then the outer + // view, so that the app bar will scroll out of the way asap. + double outerDelta = delta; + for (final _NestedScrollPosition position in _innerPositions) { + if (position.pixels < 0.0) { // This inner position is in overscroll. + final double potentialOuterDelta = position.applyClampedDragUpdate(delta); + // In case there are multiple positions in varying states of + // overscroll, the first to 'reach' the outer view above takes + // precedence. + outerDelta = math.max(outerDelta, potentialOuterDelta); + } + } + if (outerDelta != 0.0) { + final double innerDelta = _outerPosition.applyClampedDragUpdate( + outerDelta + ); + if (innerDelta != 0.0) { + for (final _NestedScrollPosition position in _innerPositions) + position.applyFullDragUpdate(innerDelta); + } } } else { - // dragging "down" - delta is positive - // prioritize the inner views, so that the inner content will move before + // Dragging "down" - delta is positive + // Prioritize the inner views, so that the inner content will move before // the app bar grows double outerDelta = 0.0; // it will go positive if it changes final List overscrolls = []; diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index 87e6bf5aaa9..c01c3116918 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -133,9 +133,7 @@ void main() { await tester.pump(const Duration(milliseconds: 20)); final Offset point2 = tester.getCenter(find.text('aaa1')); expect(point2.dy, greaterThan(point1.dy)); - // TODO(ianh): Once we improve how we handle scrolling down from overscroll, - // the following expectation should switch to 200.0. - expect(tester.renderObject(find.byType(AppBar)).size.height, 120.0); + expect(tester.renderObject(find.byType(AppBar)).size.height, 200.0); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('NestedScrollView overscroll and release and hold', (WidgetTester tester) async { @@ -173,7 +171,6 @@ void main() { await tester.pumpAndSettle(); expect(find.text('aaa2'), findsOneWidget); }, - skip: true, // https://github.com/flutter/flutter/issues/9040 variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('NestedScrollView', (WidgetTester tester) async { @@ -780,7 +777,7 @@ void main() { await tester.pump(); expect( tester.getRect(find.byKey(key1)), - const Rect.fromLTWH(0.0, -1.0, 800.0, 100.0), + const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0), ); expect(tester.getRect(find.byKey(key2)).top, greaterThan(100.0)); expect(tester.getRect(find.byKey(key2)).top, lessThan(129.0)); @@ -788,7 +785,7 @@ void main() { await tester.pump(); expect( tester.getRect(find.byKey(key1)), - const Rect.fromLTWH(0.0, -11.0, 800.0, 100.0), + const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0), ); await gesture.moveBy(const Offset(0.0, 20.0)); // overscroll again await tester.pump();