Unverified Commit 63e328f7 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Fix pointer scroll for nested NeverScrollables (#70953)

parent 97466192
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
...@@ -628,25 +629,35 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R ...@@ -628,25 +629,35 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
// SCROLL WHEEL // SCROLL WHEEL
// Returns the offset that should result from applying [event] to the current
// position, taking min/max scroll extent into account.
double _targetScrollOffsetForPointerScroll(PointerScrollEvent event) {
final double delta = _pointerSignalEventDelta(event);
return math.min(math.max(position.pixels + delta, position.minScrollExtent),
position.maxScrollExtent);
}
// Returns the delta that should result from applying [event] with axis and // Returns the delta that should result from applying [event] with axis and
// direction taken into account. // direction taken into account.
double _targetScrollDeltaForPointerScroll(PointerScrollEvent event) { double _pointerSignalEventDelta(PointerScrollEvent event) {
double delta = widget.axis == Axis.horizontal double delta = widget.axis == Axis.horizontal
? event.scrollDelta.dx ? event.scrollDelta.dx
: event.scrollDelta.dy; : event.scrollDelta.dy;
if (axisDirectionIsReversed(widget.axisDirection)) { if (axisDirectionIsReversed(widget.axisDirection)) {
delta *= -1; delta *= -1;
} }
return delta; return delta;
} }
void _receivedPointerSignal(PointerSignalEvent event) { void _receivedPointerSignal(PointerSignalEvent event) {
if (event is PointerScrollEvent && _position != null) { if (event is PointerScrollEvent && _position != null) {
final double targetScrollOffset = _targetScrollDeltaForPointerScroll(event); if (_physics != null && !_physics!.shouldAcceptUserOffset(position)) {
return;
}
final double targetScrollOffset = _targetScrollOffsetForPointerScroll(event);
// Only express interest in the event if it would actually result in a scroll. // Only express interest in the event if it would actually result in a scroll.
if (targetScrollOffset != 0) { if (targetScrollOffset != position.pixels) {
GestureBinding.instance!.pointerSignalResolver.register(event, _handlePointerScroll); GestureBinding.instance!.pointerSignalResolver.register(event, _handlePointerScroll);
} }
} }
...@@ -654,12 +665,9 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R ...@@ -654,12 +665,9 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
void _handlePointerScroll(PointerEvent event) { void _handlePointerScroll(PointerEvent event) {
assert(event is PointerScrollEvent); assert(event is PointerScrollEvent);
if (_physics != null && !_physics!.shouldAcceptUserOffset(position)) { final double targetScrollOffset = _targetScrollOffsetForPointerScroll(event as PointerScrollEvent);
return; if (targetScrollOffset != position.pixels) {
} position.pointerScroll(_pointerSignalEventDelta(event));
final double targetScrollOffset = _targetScrollDeltaForPointerScroll(event as PointerScrollEvent);
if (targetScrollOffset != 0) {
position.pointerScroll(targetScrollOffset);
} }
} }
......
...@@ -1148,6 +1148,64 @@ void main() { ...@@ -1148,6 +1148,64 @@ void main() {
expect(targetMidRightPage1, findsOneWidget); expect(targetMidRightPage1, findsOneWidget);
expect(targetMidLeftPage1, findsOneWidget); expect(targetMidLeftPage1, findsOneWidget);
}); });
testWidgets('PointerScroll on nested NeverScrollable ListView goes to outer Scrollable.', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/70948
final ScrollController outerController = ScrollController();
final ScrollController innerController = ScrollController();
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
controller: outerController,
child: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Column(
children: <Widget>[
for (int i = 0; i < 100; i++)
Text('SingleChildScrollView $i'),
]
),
Container(
height: 3000,
width: 400,
child: ListView.builder(
controller: innerController,
physics: const NeverScrollableScrollPhysics(),
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Text('Nested NeverScrollable ListView $index');
},
)
),
]
)
)
)
),
));
expect(outerController.position.pixels, 0.0);
expect(innerController.position.pixels, 0.0);
final Offset outerScrollable = tester.getCenter(find.text('SingleChildScrollView 3'));
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
// Hover over the outer scroll view and create a pointer scroll.
testPointer.hover(outerScrollable);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)));
await tester.pump(const Duration(milliseconds: 250));
expect(outerController.position.pixels, 20.0);
expect(innerController.position.pixels, 0.0);
final Offset innerScrollable = tester.getCenter(find.text('Nested NeverScrollable ListView 20'));
// Hover over the inner scroll view and create a pointer scroll.
// This inner scroll view is not scrollable, and so the outer should scroll.
testPointer.hover(innerScrollable);
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -20.0)));
await tester.pump(const Duration(milliseconds: 250));
expect(outerController.position.pixels, 0.0);
expect(innerController.position.pixels, 0.0);
});
} }
// ignore: must_be_immutable // ignore: must_be_immutable
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment