parent
6e877226bf
commit
93fd29455d
@ -535,15 +535,11 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
}
|
||||
|
||||
void _initMouseGestureRecognizer() {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
_gestureRecognizers[TapAndPanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapAndPanGestureRecognizer>(
|
||||
() => TapAndPanGestureRecognizer(debugOwner:this),
|
||||
() => TapAndPanGestureRecognizer(
|
||||
debugOwner:this,
|
||||
supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.mouse },
|
||||
),
|
||||
(TapAndPanGestureRecognizer instance) {
|
||||
instance
|
||||
..onTapDown = _startNewMouseSelectionGesture
|
||||
@ -556,9 +552,43 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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>(
|
||||
() => LongPressGestureRecognizer(debugOwner: this, supportedDevices: _kLongPressSelectionDevices),
|
||||
(LongPressGestureRecognizer instance) {
|
||||
|
@ -312,6 +312,144 @@ void main() {
|
||||
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 {
|
||||
final UniqueKey spy = UniqueKey();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
Loading…
x
Reference in New Issue
Block a user