Fix overscroll edge case that puts NestedScrollViews out of sync (#68644)
This commit is contained in:
parent
c5c2b24a07
commit
8e8f61856a
@ -1343,7 +1343,9 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele
|
|||||||
// The logic for max is equivalent but on the other side.
|
// The logic for max is equivalent but on the other side.
|
||||||
final double max = delta > 0.0
|
final double max = delta > 0.0
|
||||||
? double.infinity
|
? double.infinity
|
||||||
: math.max(maxScrollExtent, pixels);
|
// If pixels < 0.0, then we are currently in overscroll. The max should be
|
||||||
|
// 0.0, representing the end of the overscrolled portion.
|
||||||
|
: pixels < 0.0 ? 0.0 : math.max(maxScrollExtent, pixels);
|
||||||
final double oldPixels = pixels;
|
final double oldPixels = pixels;
|
||||||
final double newPixels = (pixels - delta).clamp(min, max);
|
final double newPixels = (pixels - delta).clamp(min, max);
|
||||||
final double clampedDelta = newPixels - pixels;
|
final double clampedDelta = newPixels - pixels;
|
||||||
|
@ -1906,6 +1906,61 @@ void main() {
|
|||||||
expect(tester.getCenter(find.text('Item 49')).dy, equals(585.0));
|
expect(tester.getCenter(find.text('Item 49')).dy, equals(585.0));
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/63978
|
||||||
|
testWidgets('Inner _NestedScrollPosition.applyClampedDragUpdate correctly calculates range when in overscroll', (WidgetTester tester) async {
|
||||||
|
final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey();
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: NestedScrollView(
|
||||||
|
key: nestedScrollView,
|
||||||
|
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
|
||||||
|
return <Widget>[
|
||||||
|
const SliverAppBar(
|
||||||
|
expandedHeight: 200,
|
||||||
|
title: Text('Test'),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
body: ListView.builder(
|
||||||
|
itemExtent: 100.0,
|
||||||
|
itemBuilder: (BuildContext context, int index) => Container(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Material(
|
||||||
|
color: index.isEven ? Colors.cyan : Colors.deepOrange,
|
||||||
|
child: Center(
|
||||||
|
child: Text(index.toString()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(nestedScrollView.currentState!.outerController.position.pixels, 0.0);
|
||||||
|
expect(nestedScrollView.currentState!.innerController.position.pixels, 0.0);
|
||||||
|
expect(nestedScrollView.currentState!.outerController.position.maxScrollExtent, 200.0);
|
||||||
|
final Offset point = tester.getCenter(find.text('1'));
|
||||||
|
// Drag slightly into overscroll in the inner position.
|
||||||
|
final TestGesture gesture = await tester.startGesture(point);
|
||||||
|
await gesture.moveBy(const Offset(0.0, 5.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(nestedScrollView.currentState!.outerController.position.pixels, 0.0);
|
||||||
|
expect(nestedScrollView.currentState!.innerController.position.pixels, -5.0);
|
||||||
|
// Move by a much larger delta than the amount of over scroll, in a very
|
||||||
|
// short period of time.
|
||||||
|
await gesture.moveBy(const Offset(0.0, -500.0));
|
||||||
|
await tester.pump();
|
||||||
|
// The overscrolled inner position should have closed, then passed the
|
||||||
|
// correct remaining delta to the outer position, and finally any remainder
|
||||||
|
// back to the inner position.
|
||||||
|
expect(
|
||||||
|
nestedScrollView.currentState!.outerController.position.pixels,
|
||||||
|
nestedScrollView.currentState!.outerController.position.maxScrollExtent,
|
||||||
|
);
|
||||||
|
expect(nestedScrollView.currentState!.innerController.position.pixels, 295.0);
|
||||||
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestHeader extends SliverPersistentHeaderDelegate {
|
class TestHeader extends SliverPersistentHeaderDelegate {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user