Fix pointer scroll for nested NeverScrollables (#70953)
This commit is contained in:
parent
974661924b
commit
63e328f7f0
@ -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
|
|||||||
|
|
||||||
// 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
|
|||||||
|
|
||||||
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() {
|
|||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user