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.
This commit is contained in:
Luke Curran
2019-06-12 07:40:14 -05:00
committed by Martin Yankov
parent c5db112b8d
commit b8a82f279e
3 changed files with 126 additions and 83 deletions

View File

@@ -7,43 +7,49 @@ import * as stackLayoutModule from "tns-core-modules/ui/layouts/stack-layout";
export function createPage() {
var stack = new stackLayoutModule.StackLayout();
var labelHeight = Math.round(deviceProperties.screen.mainScreen.heightPixels / (7 * deviceProperties.screen.mainScreen.scale));
var stopButton = new button.Button();
const stack = new stackLayoutModule.StackLayout();
const labelHeight = Math.round(deviceProperties.screen.mainScreen.heightPixels / (7 * deviceProperties.screen.mainScreen.scale));
const stopButton = new button.Button();
stopButton.text = "Stop Detecting Gestures";
stack.addChild(stopButton);
var tapLabel = new labelModule.Label();
const tapLabel = new labelModule.Label();
tapLabel.text = "Tap here";
stack.addChild(tapLabel);
var doubletapLabel = new labelModule.Label();
const doubletapLabel = new labelModule.Label();
doubletapLabel.text = "Double Tap here";
stack.addChild(doubletapLabel);
var longpressLabel = new labelModule.Label();
const longpressLabel = new labelModule.Label();
longpressLabel.text = "Long Press here";
stack.addChild(longpressLabel);
var swipeLabel = new labelModule.Label();
const tapAndDoubleTapLabel = new labelModule.Label();
tapAndDoubleTapLabel.height = labelHeight;
tapAndDoubleTapLabel.text = "Tap or Double Tap";
tapAndDoubleTapLabel.textWrap = true;
stack.addChild(tapAndDoubleTapLabel);
const swipeLabel = new labelModule.Label();
swipeLabel.height = labelHeight;
swipeLabel.text = "Swipe here";
swipeLabel.textWrap = true;
stack.addChild(swipeLabel);
var panLabel = new labelModule.Label();
const panLabel = new labelModule.Label();
panLabel.height = labelHeight;
panLabel.text = "Pan here";
panLabel.textWrap = true;
stack.addChild(panLabel);
var pinchLabel = new labelModule.Label();
const pinchLabel = new labelModule.Label();
pinchLabel.height = labelHeight;
pinchLabel.text = "Pinch here";
pinchLabel.textWrap = true;
stack.addChild(pinchLabel);
var rotaionLabel = new labelModule.Label();
const rotaionLabel = new labelModule.Label();
rotaionLabel.height = labelHeight;
rotaionLabel.text = "Rotate here";
rotaionLabel.textWrap = true;
@@ -57,6 +63,8 @@ export function createPage() {
observer5.disconnect();
observer6.disconnect();
observer7.disconnect();
observer8.disconnect();
observer9.disconnect();
tapLabel.text = "Gestures detection disabled";
doubletapLabel.text = "Gestures detection disabled";
longpressLabel.text = "Gestures detection disabled";
@@ -64,56 +72,69 @@ export function createPage() {
panLabel.text = "Gestures detection disabled";
pinchLabel.text = "Gestures detection disabled";
rotaionLabel.text = "Gestures detection disabled";
tapAndDoubleTapLabel.text = "Gestures detection disabled";
});
tapLabel.on(gestures.GestureTypes.tap, function (args: gestures.GestureEventData) {
tapLabel.text = "Tap gesture detected, " + (args.object === tapLabel);
});
var observer1 = tapLabel.getGestureObservers(gestures.GestureTypes.tap)[0];
const observer1 = tapLabel.getGestureObservers(gestures.GestureTypes.tap)[0];
doubletapLabel.on(gestures.GestureTypes.doubleTap, function (args: gestures.GestureEventData) {
doubletapLabel.text = "Double Tap gesture detected, " + (args.object === doubletapLabel);
});
var observer2 = doubletapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0];
const observer2 = doubletapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0];
longpressLabel.on(gestures.GestureTypes.longPress, function (args: gestures.GestureEventData) {
longpressLabel.text = "Long Press gesture detected, " + (args.object === longpressLabel);
});
var observer3 = longpressLabel.getGestureObservers(gestures.GestureTypes.longPress)[0];
const observer3 = longpressLabel.getGestureObservers(gestures.GestureTypes.longPress)[0];
swipeLabel.on(gestures.GestureTypes.swipe, function (args: gestures.SwipeGestureEventData) {
swipeLabel.text = "Swipe Direction: " + args.direction + ", " + (args.object === swipeLabel); // + getStateAsString(args.state);
});
var observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0];
const observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0];
panLabel.on(gestures.GestureTypes.pan, function (args: gestures.PanGestureEventData) {
panLabel.text = "Pan deltaX:" + Math.round(args.deltaX) + "; deltaY:" + Math.round(args.deltaY) + ";" + ", " + (args.object === panLabel) + getStateAsString(args.state);
});
var observer5 = panLabel.getGestureObservers(gestures.GestureTypes.pan)[0];
const observer5 = panLabel.getGestureObservers(gestures.GestureTypes.pan)[0];
pinchLabel.on(gestures.GestureTypes.pinch, function (args: gestures.PinchGestureEventData) {
pinchLabel.text = "Pinch Scale: " + Math.round(args.scale) + ", " + (args.object === pinchLabel) + getStateAsString(args.state);
});
var observer6 = pinchLabel.getGestureObservers(gestures.GestureTypes.pinch)[0];
const observer6 = pinchLabel.getGestureObservers(gestures.GestureTypes.pinch)[0];
rotaionLabel.on(gestures.GestureTypes.rotation, function (args: gestures.RotationGestureEventData) {
rotaionLabel.text = "Rotation: " + Math.round(args.rotation) + ", " + (args.object === rotaionLabel) + getStateAsString(args.state);
});
var observer7 = rotaionLabel.getGestureObservers(gestures.GestureTypes.rotation)[0];
const observer7 = rotaionLabel.getGestureObservers(gestures.GestureTypes.rotation)[0];
var page = new pages.Page();
tapAndDoubleTapLabel.on(gestures.GestureTypes.doubleTap, function (args: gestures.GestureEventData) {
tapAndDoubleTapLabel.text = "Last action: Double tap gesture, " + (args.object === tapAndDoubleTapLabel);
});
const observer8 = tapAndDoubleTapLabel.getGestureObservers(gestures.GestureTypes.doubleTap)[0];
tapAndDoubleTapLabel.on(gestures.GestureTypes.tap, function (args: gestures.GestureEventData) {
tapAndDoubleTapLabel.text = "Last action: Tap gesture, " + (args.object === tapAndDoubleTapLabel);
});
const observer9 = tapAndDoubleTapLabel.getGestureObservers(gestures.GestureTypes.tap)[0];
const page = new pages.Page();
page.content = stack;
return page;
}
var states = new Array<string>();
const states = new Array<string>();
function getStateAsString(state: gestures.GestureStateTypes): string {
if (state === gestures.GestureStateTypes.began) {
states.length = 0;

View File

@@ -10,6 +10,8 @@ import {
// Import layout from utils directly to avoid circular references
import { layout } from "../../utils/utils";
import * as timer from "../../timer";
export * from "./gestures-common";
interface TapAndDoubleTapGestureListener {
@@ -27,6 +29,11 @@ function initializeTapAndDoubleTapGestureListener() {
private _target: View;
private _type: number;
private _lastUpTime: number = 0;
private _tapTimeoutId: number;
private static DoubleTapTimeout = android.view.ViewConfiguration.getDoubleTapTimeout();
constructor(observer: GesturesObserver, target: View, type: number) {
super();
@@ -37,28 +44,42 @@ function initializeTapAndDoubleTapGestureListener() {
}
public onSingleTapUp(motionEvent: android.view.MotionEvent): boolean {
if (this._type & GestureTypes.tap) {
let args = _getArgs(GestureTypes.tap, this._target, motionEvent);
_executeCallback(this._observer, args);
}
return true;
}
public onDoubleTap(motionEvent: android.view.MotionEvent): boolean {
if (this._type & GestureTypes.doubleTap) {
let args = _getArgs(GestureTypes.doubleTap, this._target, motionEvent);
_executeCallback(this._observer, args);
}
this._handleSingleTap(motionEvent);
this._lastUpTime = Date.now();
return true;
}
public onDown(motionEvent: android.view.MotionEvent): boolean {
const tapTime = Date.now();
if ((tapTime - this._lastUpTime) <= TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout) {
this._handleDoubleTap(motionEvent);
}
return true;
}
public onLongPress(motionEvent: android.view.MotionEvent): void {
if (this._type & GestureTypes.longPress) {
let args = _getArgs(GestureTypes.longPress, this._target, motionEvent);
const args = _getArgs(GestureTypes.longPress, this._target, motionEvent);
_executeCallback(this._observer, args);
}
}
private _handleSingleTap(motionEvent: android.view.MotionEvent): void {
this._tapTimeoutId = timer.setTimeout(() => {
if (this._type & GestureTypes.tap) {
const args = _getArgs(GestureTypes.tap, this._target, motionEvent);
_executeCallback(this._observer, args);
}
timer.clearTimeout(this._tapTimeoutId);
}, TapAndDoubleTapGestureListenerImpl.DoubleTapTimeout);
}
private _handleDoubleTap(motionEvent: android.view.MotionEvent): void {
if (this._tapTimeoutId) {
timer.clearTimeout(this._tapTimeoutId);
}
if (this._type & GestureTypes.doubleTap) {
const args = _getArgs(GestureTypes.doubleTap, this._target, motionEvent);
_executeCallback(this._observer, args);
}
}
@@ -94,7 +115,7 @@ function initializePinchGestureListener() {
public onScaleBegin(detector: android.view.ScaleGestureDetector): boolean {
this._scale = detector.getScaleFactor();
let args = new PinchGestureEventData(
const args = new PinchGestureEventData(
this._target,
detector,
this._scale,
@@ -109,7 +130,7 @@ function initializePinchGestureListener() {
public onScale(detector: android.view.ScaleGestureDetector): boolean {
this._scale *= detector.getScaleFactor();
let args = new PinchGestureEventData(
const args = new PinchGestureEventData(
this._target,
detector,
this._scale,
@@ -123,7 +144,7 @@ function initializePinchGestureListener() {
public onScaleEnd(detector: android.view.ScaleGestureDetector): void {
this._scale *= detector.getScaleFactor();
let args = new PinchGestureEventData(
const args = new PinchGestureEventData(
this._target,
detector,
this._scale,
@@ -172,41 +193,28 @@ function initializeSwipeGestureListener() {
let deltaX = currentEvent.getX() - initialEvent.getX();
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (Math.abs(deltaX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (deltaX > 0) {
args = _getSwipeArgs(SwipeDirection.right, this._target, initialEvent, currentEvent);
_executeCallback(this._observer, args);
result = true;
} else {
args = _getSwipeArgs(SwipeDirection.left, this._target, initialEvent, currentEvent);
_executeCallback(this._observer, args);
result = true;
}
}
} else {
if (Math.abs(deltaY) > SWIPE_THRESHOLD
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (deltaY > 0) {
args = _getSwipeArgs(SwipeDirection.down, this._target, initialEvent, currentEvent);
_executeCallback(this._observer, args);
result = true;
} else {
args = _getSwipeArgs(SwipeDirection.up, this._target, initialEvent, currentEvent);
_executeCallback(this._observer, args);
result = true;
}
}
@@ -228,7 +236,7 @@ const INVALID_POINTER_ID = -1;
const TO_DEGREES = (180 / Math.PI);
export function observe(target: View, type: GestureTypes, callback: (args: GestureEventData) => void, context?: any): GesturesObserver {
let observer = new GesturesObserver(target, callback, context);
const observer = new GesturesObserver(target, callback, context);
observer.observe(type);
return observer;
}
@@ -292,7 +300,7 @@ export class GesturesObserver extends GesturesObserverBase {
private _attach(target: View, type: GestureTypes) {
this._detach();
if (type & GestureTypes.tap || type & GestureTypes.doubleTap || type & GestureTypes.longPress) {
if ((type & GestureTypes.tap) || (type & GestureTypes.doubleTap) || (type & GestureTypes.longPress)) {
initializeTapAndDoubleTapGestureListener();
this._simpleGestureDetector = new androidx.core.view.GestureDetectorCompat(target._context, new TapAndDoubleTapGestureListener(this, this.target, type));
}
@@ -465,7 +473,7 @@ class CustomPanGestureDetector {
return true;
}
private trackStop(currentEvent: android.view.MotionEvent, cahceEvent: boolean) {
private trackStop(currentEvent: android.view.MotionEvent, cacheEvent: boolean) {
if (this.isTracking) {
let args = _getPanArgs(this.deltaX, this.deltaY, this.target, GestureStateTypes.ended, null, currentEvent);
_executeCallback(this.observer, args);
@@ -475,10 +483,9 @@ class CustomPanGestureDetector {
this.isTracking = false;
}
if (cahceEvent) {
if (cacheEvent) {
this.lastEventCache = currentEvent;
}
else {
} else {
this.lastEventCache = undefined;
}
}
@@ -509,8 +516,7 @@ class CustomPanGestureDetector {
x: event.getRawX() / this.density,
y: event.getRawY() / this.density
};
}
else {
} else {
const offX = event.getRawX() - event.getX();
const offY = event.getRawY() - event.getY();
let res = { x: 0, y: 0 };
@@ -559,8 +565,7 @@ class CustomRotateGestureDetector {
if (this.trackedPtrId1 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId2) {
this.trackedPtrId1 = pointerID;
assigned = true;
}
else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) {
} else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) {
this.trackedPtrId2 = pointerID;
assigned = true;
}
@@ -585,8 +590,7 @@ class CustomRotateGestureDetector {
case android.view.MotionEvent.ACTION_POINTER_UP:
if (pointerID === this.trackedPtrId1) {
this.trackedPtrId1 = INVALID_POINTER_ID;
}
else if (pointerID === this.trackedPtrId2) {
} else if (pointerID === this.trackedPtrId2) {
this.trackedPtrId2 = INVALID_POINTER_ID;
}

View File

@@ -8,16 +8,33 @@ import { GesturesObserverBase, toString, TouchAction, GestureStateTypes, Gesture
export * from "./gestures-common";
export function observe(target: View, type: GestureTypes, callback: (args: GestureEventData) => void, context?: any): GesturesObserver {
let observer = new GesturesObserver(target, callback, context);
const observer = new GesturesObserver(target, callback, context);
observer.observe(type);
return observer;
}
class UIGestureRecognizerDelegateImpl extends NSObject implements UIGestureRecognizerDelegate {
public static ObjCProtocols = [UIGestureRecognizerDelegate];
public gestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer): boolean {
// If both gesture recognizers are of type UITapGestureRecognizer, do not allow
// simultaneous recognition.
if (gestureRecognizer instanceof UITapGestureRecognizer && otherGestureRecognizer instanceof UITapGestureRecognizer) {
return false;
}
return true;
}
public gestureRecognizerShouldRequireFailureOfGestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer): boolean {
// If both gesture recognizers are of type UITapGestureRecognizer & one of them is a doubleTap,
// we must require a failure.
if (gestureRecognizer instanceof UITapGestureRecognizer
&& otherGestureRecognizer instanceof UITapGestureRecognizer
&& otherGestureRecognizer.numberOfTapsRequired === 2) {
return true;
}
return false;
}
}
let recognizerDelegateInstance: UIGestureRecognizerDelegateImpl = <UIGestureRecognizerDelegateImpl>UIGestureRecognizerDelegateImpl.new();
@@ -32,7 +49,7 @@ class UIGestureRecognizerImpl extends NSObject {
private _context: any;
public static initWithOwnerTypeCallback(owner: WeakRef<GesturesObserver>, type: any, callback?: Function, thisArg?: any): UIGestureRecognizerImpl {
let handler = <UIGestureRecognizerImpl>UIGestureRecognizerImpl.new();
const handler = <UIGestureRecognizerImpl>UIGestureRecognizerImpl.new();
handler._owner = owner;
handler._type = type;
@@ -48,12 +65,12 @@ class UIGestureRecognizerImpl extends NSObject {
}
public recognize(recognizer: UIGestureRecognizer): void {
let owner = this._owner.get();
let callback = this._callback ? this._callback : (owner ? owner.callback : null);
let typeParam = this._type;
let target = owner ? owner.target : undefined;
const owner = this._owner.get();
const callback = this._callback ? this._callback : (owner ? owner.callback : null);
const typeParam = this._type;
const target = owner ? owner.target : undefined;
let args = {
const args = {
type: typeParam,
view: target,
ios: recognizer,
@@ -106,17 +123,14 @@ export class GesturesObserver extends GesturesObserverBase {
this._detach();
if (target && target.nativeViewProtected && target.nativeViewProtected.addGestureRecognizer) {
let nativeView = <UIView>target.nativeViewProtected;
const nativeView = <UIView>target.nativeViewProtected;
if (type & GestureTypes.tap) {
nativeView.addGestureRecognizer(this._createRecognizer(GestureTypes.tap));
}
if (type & GestureTypes.doubleTap) {
let r = <UITapGestureRecognizer>this._createRecognizer(GestureTypes.doubleTap);
r.numberOfTapsRequired = 2;
nativeView.addGestureRecognizer(r);
nativeView.addGestureRecognizer(this._createRecognizer(GestureTypes.doubleTap));
}
if (type & GestureTypes.pinch) {
@@ -203,8 +217,8 @@ export class GesturesObserver extends GesturesObserverBase {
private _createRecognizer(type: GestureTypes, callback?: (args: GestureEventData) => void, swipeDirection?: UISwipeGestureRecognizerDirection): UIGestureRecognizer {
let recognizer: UIGestureRecognizer;
let name = toString(type);
let target = _createUIGestureRecognizerTarget(this, type, callback, this.context);
let recognizerType = _getUIGestureRecognizerType(type);
const target = _createUIGestureRecognizerTarget(this, type, callback, this.context);
const recognizerType = _getUIGestureRecognizerType(type);
if (recognizerType) {
recognizer = recognizerType.alloc().initWithTargetAction(target, "recognize");
@@ -212,9 +226,10 @@ export class GesturesObserver extends GesturesObserverBase {
if (type === GestureTypes.swipe && swipeDirection) {
name = name + swipeDirection.toString();
(<UISwipeGestureRecognizer>recognizer).direction = swipeDirection;
}
else if (type === GestureTypes.touch) {
} else if (type === GestureTypes.touch) {
(<TouchGestureRecognizer>recognizer).observer = this;
} else if (type === GestureTypes.doubleTap) {
(<UITapGestureRecognizer>recognizer).numberOfTapsRequired = 2;
}
if (recognizer) {
@@ -285,8 +300,8 @@ function _getSwipeDirection(direction: UISwipeGestureRecognizerDirection): Swipe
}
function _getPinchData(args: GestureEventData): PinchGestureEventData {
let recognizer = <UIPinchGestureRecognizer>args.ios;
let center = recognizer.locationInView(args.view.nativeViewProtected);
const recognizer = <UIPinchGestureRecognizer>args.ios;
const center = recognizer.locationInView(args.view.nativeViewProtected);
return <PinchGestureEventData>{
type: args.type,
@@ -303,7 +318,7 @@ function _getPinchData(args: GestureEventData): PinchGestureEventData {
}
function _getSwipeData(args: GestureEventData): SwipeGestureEventData {
let recognizer = <UISwipeGestureRecognizer>args.ios;
const recognizer = <UISwipeGestureRecognizer>args.ios;
return <SwipeGestureEventData>{
type: args.type,
@@ -317,7 +332,8 @@ function _getSwipeData(args: GestureEventData): SwipeGestureEventData {
}
function _getPanData(args: GestureEventData, view: UIView): PanGestureEventData {
let recognizer = <UIPanGestureRecognizer>args.ios;
const recognizer = <UIPanGestureRecognizer>args.ios;
return <PanGestureEventData>{
type: args.type,
view: args.view,
@@ -332,7 +348,8 @@ function _getPanData(args: GestureEventData, view: UIView): PanGestureEventData
}
function _getRotationData(args: GestureEventData): RotationGestureEventData {
let recognizer = <UIRotationGestureRecognizer>args.ios;
const recognizer = <UIRotationGestureRecognizer>args.ios;
return <RotationGestureEventData>{
type: args.type,
view: args.view,
@@ -394,6 +411,7 @@ class Pointer implements Pointer {
private _view: View;
private _location: CGPoint;
private get location(): CGPoint {
if (!this._location) {
this._location = this.ios.locationInView(this._view.nativeViewProtected);