Skip to content

Commit b8a82f2

Browse files
MCurran16MartoYankov
authored andcommitted
feat: cancel contradictory gesture events (#7296)
BREAKING CHANGES: Old behavior: - iOS/Android: - double tap: child tap -> parent tap -> child double tap -> parent double tap - tap: child tap -> parent tap New behavior: - iOS - double tap: child double tap - tap: child tap - Android - double tap: child double tap -> parent double tap - tap: child tap -> parent tap Migration steps: Move event handlers accordingly.
1 parent c5db112 commit b8a82f2

3 files changed

Lines changed: 125 additions & 82 deletions

File tree

apps/app/ui-tests-app/events/gestures.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,49 @@ import * as stackLayoutModule from "tns-core-modules/ui/layouts/stack-layout";
77

88
export function createPage() {
99

10-
var stack = new stackLayoutModule.StackLayout();
11-
var labelHeight = Math.round(deviceProperties.screen.mainScreen.heightPixels / (7 * deviceProperties.screen.mainScreen.scale));
12-
var stopButton = new button.Button();
10+
const stack = new stackLayoutModule.StackLayout();
11+
const labelHeight = Math.round(deviceProperties.screen.mainScreen.heightPixels / (7 * deviceProperties.screen.mainScreen.scale));
12+
const stopButton = new button.Button();
1313
stopButton.text = "Stop Detecting Gestures";
1414
stack.addChild(stopButton);
1515

16-
var tapLabel = new labelModule.Label();
16+
const tapLabel = new labelModule.Label();
1717
tapLabel.text = "Tap here";
1818
stack.addChild(tapLabel);
1919

20-
var doubletapLabel = new labelModule.Label();
20+
const doubletapLabel = new labelModule.Label();
2121
doubletapLabel.text = "Double Tap here";
2222
stack.addChild(doubletapLabel);
2323

24-
var longpressLabel = new labelModule.Label();
24+
const longpressLabel = new labelModule.Label();
2525
longpressLabel.text = "Long Press here";
2626
stack.addChild(longpressLabel);
2727

28-
var swipeLabel = new labelModule.Label();
28+
const tapAndDoubleTapLabel = new labelModule.Label();
29+
tapAndDoubleTapLabel.height = labelHeight;
30+
tapAndDoubleTapLabel.text = "Tap or Double Tap";
31+
tapAndDoubleTapLabel.textWrap = true;
32+
stack.addChild(tapAndDoubleTapLabel);
33+
34+
const swipeLabel = new labelModule.Label();
2935
swipeLabel.height = labelHeight;
3036
swipeLabel.text = "Swipe here";
3137
swipeLabel.textWrap = true;
3238
stack.addChild(swipeLabel);
3339

34-
var panLabel = new labelModule.Label();
40+
const panLabel = new labelModule.Label();
3541
panLabel.height = labelHeight;
3642
panLabel.text = "Pan here";
3743
panLabel.textWrap = true;
3844
stack.addChild(panLabel);
3945

40-
var pinchLabel = new labelModule.Label();
46+
const pinchLabel = new labelModule.Label();
4147
pinchLabel.height = labelHeight;
4248
pinchLabel.text = "Pinch here";
4349
pinchLabel.textWrap = true;
4450
stack.addChild(pinchLabel);
4551

46-
var rotaionLabel = new labelModule.Label();
52+
const rotaionLabel = new labelModule.Label();
4753
rotaionLabel.height = labelHeight;
4854
rotaionLabel.text = "Rotate here";
4955
rotaionLabel.textWrap = true;
@@ -57,63 +63,78 @@ export function createPage() {
5763
observer5.disconnect();
5864
observer6.disconnect();
5965
observer7.disconnect();
66+
observer8.disconnect();
67+
observer9.disconnect();
6068
tapLabel.text = "Gestures detection disabled";
6169
doubletapLabel.text = "Gestures detection disabled";
6270
longpressLabel.text = "Gestures detection disabled";
6371
swipeLabel.text = "Gesturesd detection disabled";
6472
panLabel.text = "Gestures detection disabled";
6573
pinchLabel.text = "Gestures detection disabled";
6674
rotaionLabel.text = "Gestures detection disabled";
75+
tapAndDoubleTapLabel.text = "Gestures detection disabled";
6776
});
6877

6978
tapLabel.on(gestures.GestureTypes.tap, function (args: gestures.GestureEventData) {
7079
tapLabel.text = "Tap gesture detected, " + (args.object === tapLabel);
7180
});
7281

73-
var observer1 = tapLabel.getGestureObservers(gestures.GestureTypes.tap)[0];
82+
const observer1 = tapLabel.getGestureObservers(gestures.GestureTypes.tap)[0];
7483

7584
doubletapLabel.on(gestures.GestureTypes.doubleTap, function (args: gestures.GestureEventData) {
7685
doubletapLabel.text = "Double Tap gesture detected, " + (args.object === doubletapLabel);
7786
});
7887

79-
var observer2 = doubletapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0];
88+
const observer2 = doubletapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0];
8089

8190
longpressLabel.on(gestures.GestureTypes.longPress, function (args: gestures.GestureEventData) {
8291
longpressLabel.text = "Long Press gesture detected, " + (args.object === longpressLabel);
8392
});
8493

85-
var observer3 = longpressLabel.getGestureObservers(gestures.GestureTypes.longPress)[0];
94+
const observer3 = longpressLabel.getGestureObservers(gestures.GestureTypes.longPress)[0];
8695

8796
swipeLabel.on(gestures.GestureTypes.swipe, function (args: gestures.SwipeGestureEventData) {
8897
swipeLabel.text = "Swipe Direction: " + args.direction + ", " + (args.object === swipeLabel); // + getStateAsString(args.state);
8998
});
9099

91-
var observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0];
100+
const observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0];
92101

93102
panLabel.on(gestures.GestureTypes.pan, function (args: gestures.PanGestureEventData) {
94103
panLabel.text = "Pan deltaX:" + Math.round(args.deltaX) + "; deltaY:" + Math.round(args.deltaY) + ";" + ", " + (args.object === panLabel) + getStateAsString(args.state);
95104
});
96105

97-
var observer5 = panLabel.getGestureObservers(gestures.GestureTypes.pan)[0];
106+
const observer5 = panLabel.getGestureObservers(gestures.GestureTypes.pan)[0];
98107

99108
pinchLabel.on(gestures.GestureTypes.pinch, function (args: gestures.PinchGestureEventData) {
100109
pinchLabel.text = "Pinch Scale: " + Math.round(args.scale) + ", " + (args.object === pinchLabel) + getStateAsString(args.state);
101110
});
102111

103-
var observer6 = pinchLabel.getGestureObservers(gestures.GestureTypes.pinch)[0];
112+
const observer6 = pinchLabel.getGestureObservers(gestures.GestureTypes.pinch)[0];
104113

105114
rotaionLabel.on(gestures.GestureTypes.rotation, function (args: gestures.RotationGestureEventData) {
106115
rotaionLabel.text = "Rotation: " + Math.round(args.rotation) + ", " + (args.object === rotaionLabel) + getStateAsString(args.state);
107116
});
108117

109-
var observer7 = rotaionLabel.getGestureObservers(gestures.GestureTypes.rotation)[0];
118+
const observer7 = rotaionLabel.getGestureObservers(gestures.GestureTypes.rotation)[0];
119+
120+
tapAndDoubleTapLabel.on(gestures.GestureTypes.doubleTap, function (args: gestures.GestureEventData) {
121+
tapAndDoubleTapLabel.text = "Last action: Double tap gesture, " + (args.object === tapAndDoubleTapLabel);
122+
});
123+
124+
const observer8 = tapAndDoubleTapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0];
125+
126+
tapAndDoubleTapLabel.on(gestures.GestureTypes.tap, function (args: gestures.GestureEventData) {
127+
tapAndDoubleTapLabel.text = "Last action: Tap gesture, " + (args.object === tapAndDoubleTapLabel);
128+
});
129+
130+
const observer9 = tapAndDoubleTapLabel.getGestureObservers(gestures.GestureTypes.tap)[0];
110131

111-
var page = new pages.Page();
132+
const page = new pages.Page();
112133
page.content = stack;
113134
return page;
114135
}
115136

116-
var states = new Array<string>();
137+
const states = new Array<string>();
117138
function getStateAsString(state: gestures.GestureStateTypes): string {
118139
if (state === gestures.GestureStateTypes.began) {
119140
states.length = 0;

tns-core-modules/ui/gestures/gestures.android.ts

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
// Import layout from utils directly to avoid circular references
1111
import { layout } from "../../utils/utils";
1212

13+
import * as timer from "../../timer";
14+
1315
export * from "./gestures-common";
1416

1517
interface TapAndDoubleTapGestureListener {
@@ -27,6 +29,11 @@ function initializeTapAndDoubleTapGestureListener() {
2729
private _target: View;
2830
private _type: number;
2931

32+
private _lastUpTime: number = 0;
33+
private _tapTimeoutId: number;
34+
35+
private static DoubleTapTimeout = android.view.ViewConfiguration.getDoubleTapTimeout();
36+
3037
constructor(observer: GesturesObserver, target: View, type: number) {
3138
super();
3239

@@ -37,28 +44,42 @@ function initializeTapAndDoubleTapGestureListener() {
3744
}
3845

3946
public onSingleTapUp(motionEvent: android.view.MotionEvent): boolean {
40-
if (this._type & GestureTypes.tap) {
41-
let args = _getArgs(GestureTypes.tap, this._target, motionEvent);
42-
_executeCallback(this._observer, args);
47+
this._handleSingleTap(motionEvent);
48+
this._lastUpTime = Date.now();
49+
return true;
50+
}
51+
52+
public onDown(motionEvent: android.view.MotionEvent): boolean {
53+
const tapTime = Date.now();
54+
if ((tapTime - this._lastUpTime) <= TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout) {
55+
this._handleDoubleTap(motionEvent);
4356
}
4457
return true;
4558
}
4659

47-
public onDoubleTap(motionEvent: android.view.MotionEvent): boolean {
48-
if (this._type & GestureTypes.doubleTap) {
49-
let args = _getArgs(GestureTypes.doubleTap, this._target, motionEvent);
60+
public onLongPress(motionEvent: android.view.MotionEvent): void {
61+
if (this._type & GestureTypes.longPress) {
62+
const args = _getArgs(GestureTypes.longPress, this._target, motionEvent);
5063
_executeCallback(this._observer, args);
5164
}
52-
return true;
5365
}
5466

55-
public onDown(motionEvent: android.view.MotionEvent): boolean {
56-
return true;
67+
private _handleSingleTap(motionEvent: android.view.MotionEvent): void {
68+
this._tapTimeoutId = timer.setTimeout(() => {
69+
if (this._type & GestureTypes.tap) {
70+
const args = _getArgs(GestureTypes.tap, this._target, motionEvent);
71+
_executeCallback(this._observer, args);
72+
}
73+
timer.clearTimeout(this._tapTimeoutId);
74+
}, TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout);
5775
}
5876

59-
public onLongPress(motionEvent: android.view.MotionEvent): void {
60-
if (this._type & GestureTypes.longPress) {
61-
let args = _getArgs(GestureTypes.longPress, this._target, motionEvent);
77+
private _handleDoubleTap(motionEvent: android.view.MotionEvent): void {
78+
if (this._tapTimeoutId) {
79+
timer.clearTimeout(this._tapTimeoutId);
80+
}
81+
if (this._type & GestureTypes.doubleTap) {
82+
const args = _getArgs(GestureTypes.doubleTap, this._target, motionEvent);
6283
_executeCallback(this._observer, args);
6384
}
6485
}
@@ -94,7 +115,7 @@ function initializePinchGestureListener() {
94115
public onScaleBegin(detector: android.view.ScaleGestureDetector): boolean {
95116
this._scale = detector.getScaleFactor();
96117

97-
let args = new PinchGestureEventData(
118+
const args = new PinchGestureEventData(
98119
this._target,
99120
detector,
100121
this._scale,
@@ -109,7 +130,7 @@ function initializePinchGestureListener() {
109130
public onScale(detector: android.view.ScaleGestureDetector): boolean {
110131
this._scale *= detector.getScaleFactor();
111132

112-
let args = new PinchGestureEventData(
133+
const args = new PinchGestureEventData(
113134
this._target,
114135
detector,
115136
this._scale,
@@ -123,7 +144,7 @@ function initializePinchGestureListener() {
123144
public onScaleEnd(detector: android.view.ScaleGestureDetector): void {
124145
this._scale *= detector.getScaleFactor();
125146

126-
let args = new PinchGestureEventData(
147+
const args = new PinchGestureEventData(
127148
this._target,
128149
detector,
129150
this._scale,
@@ -172,41 +193,28 @@ function initializeSwipeGestureListener() {
172193
let deltaX = currentEvent.getX() - initialEvent.getX();
173194

174195
if (Math.abs(deltaX) > Math.abs(deltaY)) {
175-
176196
if (Math.abs(deltaX) > SWIPE_THRESHOLD
177197
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
178-
179198
if (deltaX > 0) {
180-
181199
args = _getSwipeArgs(SwipeDirection.right, this._target, initialEvent, currentEvent);
182200
_executeCallback(this._observer, args);
183-
184201
result = true;
185202
} else {
186-
187203
args = _getSwipeArgs(SwipeDirection.left, this._target, initialEvent, currentEvent);
188204
_executeCallback(this._observer, args);
189-
190205
result = true;
191206
}
192207
}
193-
194208
} else {
195-
196209
if (Math.abs(deltaY) > SWIPE_THRESHOLD
197210
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
198-
199211
if (deltaY > 0) {
200-
201212
args = _getSwipeArgs(SwipeDirection.down, this._target, initialEvent, currentEvent);
202213
_executeCallback(this._observer, args);
203-
204214
result = true;
205215
} else {
206-
207216
args = _getSwipeArgs(SwipeDirection.up, this._target, initialEvent, currentEvent);
208217
_executeCallback(this._observer, args);
209-
210218
result = true;
211219
}
212220
}
@@ -228,7 +236,7 @@ const INVALID_POINTER_ID = -1;
228236
const TO_DEGREES = (180 / Math.PI);
229237

230238
export function observe(target: View, type: GestureTypes, callback: (args: GestureEventData) => void, context?: any): GesturesObserver {
231-
let observer = new GesturesObserver(target, callback, context);
239+
const observer = new GesturesObserver(target, callback, context);
232240
observer.observe(type);
233241
return observer;
234242
}
@@ -292,7 +300,7 @@ export class GesturesObserver extends GesturesObserverBase {
292300
private _attach(target: View, type: GestureTypes) {
293301
this._detach();
294302

295-
if (type & GestureTypes.tap || type & GestureTypes.doubleTap || type & GestureTypes.longPress) {
303+
if ((type & GestureTypes.tap) || (type & GestureTypes.doubleTap) || (type & GestureTypes.longPress)) {
296304
initializeTapAndDoubleTapGestureListener();
297305
this._simpleGestureDetector = new androidx.core.view.GestureDetectorCompat(target._context, new TapAndDoubleTapGestureListener(this, this.target, type));
298306
}
@@ -465,7 +473,7 @@ class CustomPanGestureDetector {
465473
return true;
466474
}
467475

468-
private trackStop(currentEvent: android.view.MotionEvent, cahceEvent: boolean) {
476+
private trackStop(currentEvent: android.view.MotionEvent, cacheEvent: boolean) {
469477
if (this.isTracking) {
470478
let args = _getPanArgs(this.deltaX, this.deltaY, this.target, GestureStateTypes.ended, null, currentEvent);
471479
_executeCallback(this.observer, args);
@@ -475,10 +483,9 @@ class CustomPanGestureDetector {
475483
this.isTracking = false;
476484
}
477485

478-
if (cahceEvent) {
486+
if (cacheEvent) {
479487
this.lastEventCache = currentEvent;
480-
}
481-
else {
488+
} else {
482489
this.lastEventCache = undefined;
483490
}
484491
}
@@ -509,8 +516,7 @@ class CustomPanGestureDetector {
509516
x: event.getRawX() / this.density,
510517
y: event.getRawY() / this.density
511518
};
512-
}
513-
else {
519+
} else {
514520
const offX = event.getRawX() - event.getX();
515521
const offY = event.getRawY() - event.getY();
516522
let res = { x: 0, y: 0 };
@@ -559,8 +565,7 @@ class CustomRotateGestureDetector {
559565
if (this.trackedPtrId1 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId2) {
560566
this.trackedPtrId1 = pointerID;
561567
assigned = true;
562-
}
563-
else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) {
568+
} else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) {
564569
this.trackedPtrId2 = pointerID;
565570
assigned = true;
566571
}
@@ -585,8 +590,7 @@ class CustomRotateGestureDetector {
585590
case android.view.MotionEvent.ACTION_POINTER_UP:
586591
if (pointerID === this.trackedPtrId1) {
587592
this.trackedPtrId1 = INVALID_POINTER_ID;
588-
}
589-
else if (pointerID === this.trackedPtrId2) {
593+
} else if (pointerID === this.trackedPtrId2) {
590594
this.trackedPtrId2 = INVALID_POINTER_ID;
591595
}
592596

0 commit comments

Comments
 (0)