parent
6e877226bf
commit
93fd29455d
@ -535,30 +535,60 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _initMouseGestureRecognizer() {
|
void _initMouseGestureRecognizer() {
|
||||||
switch (defaultTargetPlatform) {
|
_gestureRecognizers[TapAndPanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapAndPanGestureRecognizer>(
|
||||||
case TargetPlatform.android:
|
() => TapAndPanGestureRecognizer(
|
||||||
case TargetPlatform.fuchsia:
|
debugOwner:this,
|
||||||
case TargetPlatform.iOS:
|
supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.mouse },
|
||||||
case TargetPlatform.linux:
|
),
|
||||||
case TargetPlatform.macOS:
|
(TapAndPanGestureRecognizer instance) {
|
||||||
case TargetPlatform.windows:
|
instance
|
||||||
_gestureRecognizers[TapAndPanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapAndPanGestureRecognizer>(
|
..onTapDown = _startNewMouseSelectionGesture
|
||||||
() => TapAndPanGestureRecognizer(debugOwner:this),
|
..onTapUp = _handleMouseTapUp
|
||||||
(TapAndPanGestureRecognizer instance) {
|
..onDragStart = _handleMouseDragStart
|
||||||
instance
|
..onDragUpdate = _handleMouseDragUpdate
|
||||||
..onTapDown = _startNewMouseSelectionGesture
|
..onDragEnd = _handleMouseDragEnd
|
||||||
..onTapUp = _handleMouseTapUp
|
..onCancel = _clearSelection
|
||||||
..onDragStart = _handleMouseDragStart
|
..dragStartBehavior = DragStartBehavior.down;
|
||||||
..onDragUpdate = _handleMouseDragUpdate
|
},
|
||||||
..onDragEnd = _handleMouseDragEnd
|
);
|
||||||
..onCancel = _clearSelection
|
|
||||||
..dragStartBehavior = DragStartBehavior.down;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initTouchGestureRecognizer() {
|
void _initTouchGestureRecognizer() {
|
||||||
|
// A [TapAndHorizontalDragGestureRecognizer] is used on non-precise pointer devices
|
||||||
|
// like PointerDeviceKind.touch so [SelectableRegion] gestures do not conflict with
|
||||||
|
// ancestor Scrollable gestures in common scenarios like a vertically scrolling list view.
|
||||||
|
_gestureRecognizers[TapAndHorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapAndHorizontalDragGestureRecognizer>(
|
||||||
|
() => TapAndHorizontalDragGestureRecognizer(
|
||||||
|
debugOwner:this,
|
||||||
|
supportedDevices: PointerDeviceKind.values.where((PointerDeviceKind device) {
|
||||||
|
return device != PointerDeviceKind.mouse;
|
||||||
|
}).toSet(),
|
||||||
|
),
|
||||||
|
(TapAndHorizontalDragGestureRecognizer instance) {
|
||||||
|
instance
|
||||||
|
// iOS does not provide a device specific touch slop
|
||||||
|
// unlike Android (~8.0), so the touch slop for a [Scrollable]
|
||||||
|
// always default to kTouchSlop which is 18.0. When
|
||||||
|
// [SelectableRegion] is the child of a horizontal
|
||||||
|
// scrollable that means the [SelectableRegion] will
|
||||||
|
// always win the gesture arena when competing with
|
||||||
|
// the ancestor scrollable because they both have
|
||||||
|
// the same touch slop threshold and the child receives
|
||||||
|
// the [PointerEvent] first. To avoid this conflict
|
||||||
|
// and ensure a smooth scrolling experience, on
|
||||||
|
// iOS the [TapAndHorizontalDragGestureRecognizer]
|
||||||
|
// will wait for all other gestures to lose before
|
||||||
|
// declaring victory.
|
||||||
|
..eagerVictoryOnDrag = defaultTargetPlatform != TargetPlatform.iOS
|
||||||
|
..onTapDown = _startNewMouseSelectionGesture
|
||||||
|
..onTapUp = _handleMouseTapUp
|
||||||
|
..onDragStart = _handleMouseDragStart
|
||||||
|
..onDragUpdate = _handleMouseDragUpdate
|
||||||
|
..onDragEnd = _handleMouseDragEnd
|
||||||
|
..onCancel = _clearSelection
|
||||||
|
..dragStartBehavior = DragStartBehavior.down;
|
||||||
|
},
|
||||||
|
);
|
||||||
_gestureRecognizers[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
_gestureRecognizers[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
||||||
() => LongPressGestureRecognizer(debugOwner: this, supportedDevices: _kLongPressSelectionDevices),
|
() => LongPressGestureRecognizer(debugOwner: this, supportedDevices: _kLongPressSelectionDevices),
|
||||||
(LongPressGestureRecognizer instance) {
|
(LongPressGestureRecognizer instance) {
|
||||||
|
@ -312,6 +312,144 @@ void main() {
|
|||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Horizontal PageView beats SelectionArea child touch drag gestures on iOS', (WidgetTester tester) async {
|
||||||
|
final PageController pageController = PageController();
|
||||||
|
const String testValue = 'abc def ghi jkl mno pqr stu vwx yz';
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
addTearDown(focusNode.dispose);
|
||||||
|
addTearDown(pageController.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: PageView(
|
||||||
|
controller: pageController,
|
||||||
|
children: <Widget>[
|
||||||
|
Center(
|
||||||
|
child: SelectableRegion(
|
||||||
|
focusNode: focusNode,
|
||||||
|
selectionControls: materialTextSelectionControls,
|
||||||
|
child: const Text(testValue),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 200.0,
|
||||||
|
child: Center(
|
||||||
|
child: Text('Page 2'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text(testValue), matching: find.byType(RichText)));
|
||||||
|
final Offset gPos = textOffsetToPosition(paragraph, testValue.indexOf('g'));
|
||||||
|
final Offset pPos = textOffsetToPosition(paragraph, testValue.indexOf('p'));
|
||||||
|
|
||||||
|
// A double tap + drag should take precendence over parent drags.
|
||||||
|
final TestGesture gesture = await tester.startGesture(gPos);
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.down(gPos);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await gesture.moveTo(pPos);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(paragraph.selections, isNotEmpty);
|
||||||
|
expect(paragraph.selections[0], TextSelection(baseOffset: testValue.indexOf('g'), extentOffset: testValue.indexOf('p') + 3));
|
||||||
|
|
||||||
|
expect(pageController.page, isNotNull);
|
||||||
|
expect(pageController.page, 0.0);
|
||||||
|
// A horizontal drag directly on the SelectableRegion should move the page
|
||||||
|
// view to the next page.
|
||||||
|
final Rect selectableTextRect = tester.getRect(find.byType(SelectableRegion));
|
||||||
|
await tester.dragFrom(selectableTextRect.centerRight - const Offset(0.1, 0.0), const Offset(-500.0, 0.0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(pageController.page, isNotNull);
|
||||||
|
expect(pageController.page, 1.0);
|
||||||
|
},
|
||||||
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
||||||
|
skip: kIsWeb, // https://github.com/flutter/flutter/issues/125582.
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('Vertical PageView beats SelectionArea child touch drag gestures', (WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/150897.
|
||||||
|
final PageController pageController = PageController();
|
||||||
|
const String testValue = 'abc def ghi jkl mno pqr stu vwx yz';
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
addTearDown(focusNode.dispose);
|
||||||
|
addTearDown(pageController.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: PageView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
controller: pageController,
|
||||||
|
children: <Widget>[
|
||||||
|
Center(
|
||||||
|
child: SelectableRegion(
|
||||||
|
focusNode: focusNode,
|
||||||
|
selectionControls: materialTextSelectionControls,
|
||||||
|
child: const Text(testValue),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 200.0,
|
||||||
|
child: Center(
|
||||||
|
child: Text('Page 2'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text(testValue), matching: find.byType(RichText)));
|
||||||
|
final Offset gPos = textOffsetToPosition(paragraph, testValue.indexOf('g'));
|
||||||
|
final Offset pPos = textOffsetToPosition(paragraph, testValue.indexOf('p'));
|
||||||
|
|
||||||
|
// A double tap + drag should take precendence over parent drags.
|
||||||
|
final TestGesture gesture = await tester.startGesture(gPos);
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.down(gPos);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await gesture.moveTo(pPos);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(paragraph.selections, isNotEmpty);
|
||||||
|
expect(paragraph.selections[0], TextSelection(baseOffset: testValue.indexOf('g'), extentOffset: testValue.indexOf('p') + 3));
|
||||||
|
|
||||||
|
expect(pageController.page, isNotNull);
|
||||||
|
expect(pageController.page, 0.0);
|
||||||
|
// A vertical drag directly on the SelectableRegion should move the page
|
||||||
|
// view to the next page.
|
||||||
|
final Rect selectableTextRect = tester.getRect(find.byType(SelectableRegion));
|
||||||
|
// Simulate a pan by drag vertically first.
|
||||||
|
await gesture.down(selectableTextRect.center);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.moveTo(selectableTextRect.center + const Offset(0.0, -200.0));
|
||||||
|
// Introduce horizontal movement.
|
||||||
|
await gesture.moveTo(selectableTextRect.center + const Offset(5.0, -300.0));
|
||||||
|
await gesture.moveTo(selectableTextRect.center + const Offset(-10.0, -400.0));
|
||||||
|
// Continue dragging vertically.
|
||||||
|
await gesture.moveTo(selectableTextRect.center + const Offset(0.0, -500.0));
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(pageController.page, isNotNull);
|
||||||
|
expect(pageController.page, 1.0);
|
||||||
|
},
|
||||||
|
variant: TargetPlatformVariant.mobile(),
|
||||||
|
skip: kIsWeb, // https://github.com/flutter/flutter/issues/125582.
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('mouse single-click selection collapses the selection', (WidgetTester tester) async {
|
testWidgets('mouse single-click selection collapses the selection', (WidgetTester tester) async {
|
||||||
final UniqueKey spy = UniqueKey();
|
final UniqueKey spy = UniqueKey();
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user