From d9ffd87aedc5655090377f698b38b9c33e602360 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Tue, 17 Nov 2015 18:20:30 +0200 Subject: [PATCH 1/6] Fix pan gesture state in android --- apps/ui-tests-app/pages/gestures.ts | 2 +- ui/gestures/gestures.android.ts | 56 ++++++++++++++++++++++------- ui/gestures/gestures.d.ts | 2 +- ui/gestures/gestures.ios.ts | 1 - 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/apps/ui-tests-app/pages/gestures.ts b/apps/ui-tests-app/pages/gestures.ts index 3952cf103..3c9f7ea50 100644 --- a/apps/ui-tests-app/pages/gestures.ts +++ b/apps/ui-tests-app/pages/gestures.ts @@ -85,7 +85,7 @@ export function createPage() { var 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); + swipeLabel.text = "Swipe Direction: " + args.direction + ", " + (args.object === swipeLabel);// + getStateAsString(args.state); }); var observer4 = swipeLabel.getGestureObservers(gestures.GestureTypes.swipe)[0]; diff --git a/ui/gestures/gestures.android.ts b/ui/gestures/gestures.android.ts index bf6967f2a..d6d83a42b 100644 --- a/ui/gestures/gestures.android.ts +++ b/ui/gestures/gestures.android.ts @@ -3,6 +3,7 @@ import definition = require("ui/gestures"); import observable = require("data/observable"); import view = require("ui/core/view"); import trace = require("trace"); +import utils = require("utils/utils"); global.moduleMerge(common, exports); @@ -11,10 +12,11 @@ var SWIPE_VELOCITY_THRESHOLD = 100; export class GesturesObserver extends common.GesturesObserver { private _onTouchListener: android.view.View.OnTouchListener; - public _simpleGestureDetector: android.view.GestureDetector; - public _scaleGestureDetector: android.view.ScaleGestureDetector; - public _swipeGestureDetector: android.view.GestureDetector; - public _panGestureDetector: android.view.GestureDetector + private _simpleGestureDetector: android.view.GestureDetector; + private _scaleGestureDetector: android.view.ScaleGestureDetector; + private _swipeGestureDetector: android.view.GestureDetector; + private _panGestureDetector: android.view.GestureDetector + private _panGestureListener: PanGestureListener; private _onTargetLoaded: (data: observable.EventData) => void; private _onTargetUnloaded: (data: observable.EventData) => void; @@ -62,6 +64,7 @@ export class GesturesObserver extends common.GesturesObserver { this._scaleGestureDetector = null; this._swipeGestureDetector = null; this._panGestureDetector = null; + this._panGestureListener = null; } private _attach(target: view.View, type: definition.GestureTypes) { @@ -81,7 +84,8 @@ export class GesturesObserver extends common.GesturesObserver { } if (type & definition.GestureTypes.pan) { - this._panGestureDetector = new android.support.v4.view.GestureDetectorCompat(target._context, new PanGestureListener(this, this.target)); + this._panGestureListener = new PanGestureListener(this, this.target); + this._panGestureDetector = new android.support.v4.view.GestureDetectorCompat(target._context, this._panGestureListener); } } @@ -100,6 +104,10 @@ export class GesturesObserver extends common.GesturesObserver { if (this._panGestureDetector) { this._panGestureDetector.onTouchEvent(motionEvent); + + if (motionEvent.getActionMasked() === android.view.MotionEvent.ACTION_UP) { + this._panGestureListener.onTouchUpAction(motionEvent); + } } if (this.type & definition.GestureTypes.rotation && motionEvent.getPointerCount() === 2) { @@ -129,7 +137,7 @@ export class GesturesObserver extends common.GesturesObserver { } } -function getState(e: android.view.MotionEvent) { +function getState(e: android.view.MotionEvent): common.GestureStateTypes { if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) { return common.GestureStateTypes.began; } else if (e.getAction() === android.view.MotionEvent.ACTION_CANCEL) { @@ -162,11 +170,10 @@ function _getSwipeArgs(direction: definition.SwipeDirection, view: view.View, ios: undefined, object: view, eventName: definition.toString(definition.GestureTypes.swipe), - state: getState(currentEvent) }; } -function _getPanArgs(deltaX: number, deltaY: number, view: view.View, +function _getPanArgs(deltaX: number, deltaY: number, view: view.View, state: common.GestureStateTypes, initialEvent: android.view.MotionEvent, currentEvent: android.view.MotionEvent): definition.PanGestureEventData { return { type: definition.GestureTypes.pan, @@ -177,7 +184,7 @@ function _getPanArgs(deltaX: number, deltaY: number, view: view.View, ios: undefined, object: view, eventName: definition.toString(definition.GestureTypes.pan), - state: getState(currentEvent) + state: state }; } @@ -367,12 +374,18 @@ class SwipeGestureListener extends android.view.GestureDetector.SimpleOnGestureL class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureListener { private _observer: GesturesObserver; private _target: view.View; + private _isScrolling: boolean; + private _deltaX: number; + private _deltaY: number; + private _density: number; constructor(observer: GesturesObserver, target: view.View) { super(); this._observer = observer; this._target = target; + this._isScrolling = false; + this._density = utils.layout.getDisplayDensity(); return global.__native(this); } @@ -382,10 +395,29 @@ class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureLis } public onScroll(initialEvent: android.view.MotionEvent, currentEvent: android.view.MotionEvent, lastDeltaX: number, lastDeltaY: number): boolean { - var deltaX = currentEvent.getX() - initialEvent.getX(); - var deltaY = currentEvent.getY() - initialEvent.getY(); - var args = _getPanArgs(deltaX, deltaY, this._target, initialEvent, currentEvent); + this._deltaX = (currentEvent.getX() - initialEvent.getX()) / this._density; + this._deltaY = (currentEvent.getY() - initialEvent.getY()) / this._density; + + if (!this._isScrolling) { + let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.began, initialEvent, currentEvent); + _executeCallback(this._observer, args); + this._isScrolling = true; + } + + let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.changed, initialEvent, currentEvent); _executeCallback(this._observer, args); return true; } + + public onTouchUpAction(currentEvent: android.view.MotionEvent) { + if (!this._isScrolling) { + return; + } + + var args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.ended, null, currentEvent); + _executeCallback(this._observer, args); + this._isScrolling = false; + this._deltaX = undefined; + this._deltaY = undefined; + } } diff --git a/ui/gestures/gestures.d.ts b/ui/gestures/gestures.d.ts index 2c8c14232..c6b3ee6ba 100644 --- a/ui/gestures/gestures.d.ts +++ b/ui/gestures/gestures.d.ts @@ -122,7 +122,7 @@ declare module "ui/gestures" { /** * Provides gesture event data for swipe gesture. */ - export interface SwipeGestureEventData extends GestureEventDataWithState { + export interface SwipeGestureEventData extends GestureEventData { direction: SwipeDirection; } diff --git a/ui/gestures/gestures.ios.ts b/ui/gestures/gestures.ios.ts index bd3cc623f..a78fa84bc 100644 --- a/ui/gestures/gestures.ios.ts +++ b/ui/gestures/gestures.ios.ts @@ -286,7 +286,6 @@ function _getSwipeData(args: definition.GestureEventData): definition.SwipeGestu direction: _getSwipeDirection(recognizer.direction), object: args.view, eventName: definition.toString(args.type), - state: getState(recognizer) }; } From ca8ec8b07963f04ae184f701c304acc02e9321d2 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 18 Nov 2015 17:08:04 +0200 Subject: [PATCH 2/6] Android: Implemented RotateGestureDetector that respects pointer ids --- ui/gestures/gestures.android.ts | 187 +++++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 51 deletions(-) diff --git a/ui/gestures/gestures.android.ts b/ui/gestures/gestures.android.ts index d6d83a42b..d15468f00 100644 --- a/ui/gestures/gestures.android.ts +++ b/ui/gestures/gestures.android.ts @@ -7,8 +7,10 @@ import utils = require("utils/utils"); global.moduleMerge(common, exports); -var SWIPE_THRESHOLD = 100; -var SWIPE_VELOCITY_THRESHOLD = 100; +const SWIPE_THRESHOLD = 100; +const SWIPE_VELOCITY_THRESHOLD = 100; +const INVALID_POINTER_ID = -1; +const TO_DEGREES = (180 / Math.PI); export class GesturesObserver extends common.GesturesObserver { private _onTouchListener: android.view.View.OnTouchListener; @@ -17,6 +19,7 @@ export class GesturesObserver extends common.GesturesObserver { private _swipeGestureDetector: android.view.GestureDetector; private _panGestureDetector: android.view.GestureDetector private _panGestureListener: PanGestureListener; + private _rotateGestureDetector: RotateGestureDetector; private _onTargetLoaded: (data: observable.EventData) => void; private _onTargetUnloaded: (data: observable.EventData) => void; @@ -65,6 +68,7 @@ export class GesturesObserver extends common.GesturesObserver { this._swipeGestureDetector = null; this._panGestureDetector = null; this._panGestureListener = null; + this._rotateGestureDetector = null; } private _attach(target: view.View, type: definition.GestureTypes) { @@ -87,6 +91,10 @@ export class GesturesObserver extends common.GesturesObserver { this._panGestureListener = new PanGestureListener(this, this.target); this._panGestureDetector = new android.support.v4.view.GestureDetectorCompat(target._context, this._panGestureListener); } + + if (type & definition.GestureTypes.rotation) { + this._rotateGestureDetector = new RotateGestureDetector(this, this.target); + } } public androidOnTouchEvent(motionEvent: android.view.MotionEvent) { @@ -110,41 +118,20 @@ export class GesturesObserver extends common.GesturesObserver { } } - if (this.type & definition.GestureTypes.rotation && motionEvent.getPointerCount() === 2) { - - var deltaX = motionEvent.getX(0) - motionEvent.getX(1); - var deltaY = motionEvent.getY(0) - motionEvent.getY(1); - var radians = Math.atan(deltaY / deltaX); - var degrees = radians * (180 / Math.PI); - - var args = { - type: definition.GestureTypes.rotation, - view: this.target, - android: motionEvent, - rotation: degrees, - ios: undefined, - object: this.target, - eventName: definition.toString(definition.GestureTypes.rotation), - state: getState(motionEvent) - } - - //var observer = that.get(); - if (this.callback) { - this.callback.call(this.context, args); - } - + if (this._rotateGestureDetector) { + this._rotateGestureDetector.onTouchEvent(motionEvent); } } } function getState(e: android.view.MotionEvent): common.GestureStateTypes { - if (e.getAction() === android.view.MotionEvent.ACTION_DOWN) { + if (e.getActionMasked() === android.view.MotionEvent.ACTION_DOWN) { return common.GestureStateTypes.began; - } else if (e.getAction() === android.view.MotionEvent.ACTION_CANCEL) { + } else if (e.getActionMasked() === android.view.MotionEvent.ACTION_CANCEL) { return common.GestureStateTypes.cancelled; - } else if (e.getAction() === android.view.MotionEvent.ACTION_MOVE) { + } else if (e.getActionMasked() === android.view.MotionEvent.ACTION_MOVE) { return common.GestureStateTypes.changed; - } else if (e.getAction() === android.view.MotionEvent.ACTION_UP) { + } else if (e.getActionMasked() === android.view.MotionEvent.ACTION_UP) { return common.GestureStateTypes.ended; } } @@ -239,8 +226,7 @@ class TapAndDoubleTapGestureListener extends android.view.GestureDetector.Simple class PinchGestureListener extends android.view.ScaleGestureDetector.SimpleOnScaleGestureListener { private _observer: GesturesObserver; private _target: view.View; - private _state: common.GestureStateTypes; - + private _scale: number; constructor(observer: GesturesObserver, target: view.View) { super(); @@ -250,28 +236,14 @@ class PinchGestureListener extends android.view.ScaleGestureDetector.SimpleOnSca return global.__native(this); } - public onScale(detector: android.view.ScaleGestureDetector): boolean { - var args = { - type: definition.GestureTypes.pinch, - view: this._target, - android: detector, - scale: detector.getScaleFactor(), - object: this._target, - eventName: definition.toString(definition.GestureTypes.pinch), - ios: undefined, - state: common.GestureStateTypes.changed - }; - - _executeCallback(this._observer, args); - return true; - } - public onScaleBegin(detector: android.view.ScaleGestureDetector): boolean { + this._scale = detector.getScaleFactor(); + var args = { type: definition.GestureTypes.pinch, view: this._target, android: detector, - scale: detector.getScaleFactor(), + scale: this._scale, object: this._target, eventName: definition.toString(definition.GestureTypes.pinch), ios: undefined, @@ -283,12 +255,32 @@ class PinchGestureListener extends android.view.ScaleGestureDetector.SimpleOnSca return true; } - public onScaleEnd(detector: android.view.ScaleGestureDetector): void { + public onScale(detector: android.view.ScaleGestureDetector): boolean { + this._scale *= detector.getScaleFactor(); + var args = { type: definition.GestureTypes.pinch, view: this._target, android: detector, - scale: detector.getScaleFactor(), + scale: this._scale, + object: this._target, + eventName: definition.toString(definition.GestureTypes.pinch), + ios: undefined, + state: common.GestureStateTypes.changed + }; + + _executeCallback(this._observer, args); + return true; + } + + public onScaleEnd(detector: android.view.ScaleGestureDetector): void { + this._scale *= detector.getScaleFactor(); + + var args = { + type: definition.GestureTypes.pinch, + view: this._target, + android: detector, + scale: this._scale, object: this._target, eventName: definition.toString(definition.GestureTypes.pinch), ios: undefined, @@ -399,7 +391,7 @@ class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureLis this._deltaY = (currentEvent.getY() - initialEvent.getY()) / this._density; if (!this._isScrolling) { - let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.began, initialEvent, currentEvent); + let args = _getPanArgs(0, 0, this._target, common.GestureStateTypes.began, initialEvent, currentEvent); _executeCallback(this._observer, args); this._isScrolling = true; } @@ -421,3 +413,96 @@ class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureLis this._deltaY = undefined; } } + +class RotateGestureDetector { + private observer: GesturesObserver; + private target: view.View; + private ptrID1: number; + private ptrID2: number; + + private initalPointersAngle: number; + private angle: number; + + constructor(observer: GesturesObserver, target: view.View) { + this.observer = observer; + this.target = target; + + this.ptrID1 = INVALID_POINTER_ID; + this.ptrID2 = INVALID_POINTER_ID; + } + + public onTouchEvent(event: android.view.MotionEvent) { + + switch (event.getActionMasked()) { + case android.view.MotionEvent.ACTION_DOWN: + this.ptrID1 = event.getPointerId(event.getActionIndex()); + break; + case android.view.MotionEvent.ACTION_POINTER_DOWN: + this.ptrID2 = event.getPointerId(event.getActionIndex()); + this.initalPointersAngle = this.getPointersAngle(event); + + this.executeCallback(event, common.GestureStateTypes.began); + break; + case android.view.MotionEvent.ACTION_MOVE: + if (this.ptrID1 !==INVALID_POINTER_ID && this.ptrID2 !== INVALID_POINTER_ID) { + this.updateAngle(event); + + this.executeCallback(event, common.GestureStateTypes.changed); + } + break; + case android.view.MotionEvent.ACTION_UP: + this.ptrID1 = INVALID_POINTER_ID; + break; + case android.view.MotionEvent.ACTION_POINTER_UP: + this.ptrID2 = INVALID_POINTER_ID; + + this.executeCallback(event, common.GestureStateTypes.ended); + break; + case android.view.MotionEvent.ACTION_CANCEL: + this.ptrID1 = INVALID_POINTER_ID; + this.ptrID2 = INVALID_POINTER_ID; + + this.executeCallback(event, common.GestureStateTypes.cancelled); + break; + } + return true; + } + + private executeCallback(event: android.view.MotionEvent, state: common.GestureStateTypes) { + var args = { + type: definition.GestureTypes.rotation, + view: this.target, + android: event, + rotation: this.angle, + ios: undefined, + object: this.target, + eventName: definition.toString(definition.GestureTypes.rotation), + state: state + } + + _executeCallback(this.observer, args); + } + + private updateAngle(event: android.view.MotionEvent) { + var newPointersAngle = this.getPointersAngle(event); + var result = ((newPointersAngle - this.initalPointersAngle) * TO_DEGREES) % 360; + + if (result < -180) { + result += 360; + } + if (result > 180) { + result -= 360; + } + + this.angle = result; + } + + private getPointersAngle(event: android.view.MotionEvent) { + let firstX = event.getX(event.findPointerIndex(this.ptrID1)); + let firstY = event.getY(event.findPointerIndex(this.ptrID1)); + let secondX = event.getX(event.findPointerIndex(this.ptrID2)); + let secondY = event.getY(event.findPointerIndex(this.ptrID2)); + + return Math.atan2((secondY - firstY), (secondX - firstX)); + } +} \ No newline at end of file From 13907fdd84d0df41cff8b7ab25cf251773c5b9f2 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 18 Nov 2015 18:34:12 +0200 Subject: [PATCH 3/6] Improved handling multitouch for pan gesture. --- ui/gestures/gestures.android.ts | 68 +++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/ui/gestures/gestures.android.ts b/ui/gestures/gestures.android.ts index d15468f00..4fd6142b3 100644 --- a/ui/gestures/gestures.android.ts +++ b/ui/gestures/gestures.android.ts @@ -113,7 +113,8 @@ export class GesturesObserver extends common.GesturesObserver { if (this._panGestureDetector) { this._panGestureDetector.onTouchEvent(motionEvent); - if (motionEvent.getActionMasked() === android.view.MotionEvent.ACTION_UP) { + if (motionEvent.getActionMasked() === android.view.MotionEvent.ACTION_UP || + motionEvent.getActionMasked() === android.view.MotionEvent.ACTION_POINTER_UP) { this._panGestureListener.onTouchUpAction(motionEvent); } } @@ -366,17 +367,21 @@ class SwipeGestureListener extends android.view.GestureDetector.SimpleOnGestureL class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureListener { private _observer: GesturesObserver; private _target: view.View; - private _isScrolling: boolean; + + private _density: number; private _deltaX: number; private _deltaY: number; - private _density: number; + + private _trackedPointerID: number; + private _initialPointerX: number; + private _initialPointerY: number; constructor(observer: GesturesObserver, target: view.View) { super(); this._observer = observer; this._target = target; - this._isScrolling = false; + this._trackedPointerID = INVALID_POINTER_ID; this._density = utils.layout.getDisplayDensity(); return global.__native(this); @@ -387,30 +392,51 @@ class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureLis } public onScroll(initialEvent: android.view.MotionEvent, currentEvent: android.view.MotionEvent, lastDeltaX: number, lastDeltaY: number): boolean { - this._deltaX = (currentEvent.getX() - initialEvent.getX()) / this._density; - this._deltaY = (currentEvent.getY() - initialEvent.getY()) / this._density; + let pointerID = currentEvent.getPointerId(currentEvent.getActionIndex()); - if (!this._isScrolling) { - let args = _getPanArgs(0, 0, this._target, common.GestureStateTypes.began, initialEvent, currentEvent); - _executeCallback(this._observer, args); - this._isScrolling = true; + if (this._trackedPointerID !== INVALID_POINTER_ID && this._trackedPointerID !== pointerID) { + // New pointer - stop tracking the old one. + this.stopTrackingCurrentPointer(currentEvent); } - let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.changed, initialEvent, currentEvent); - _executeCallback(this._observer, args); + if (this._trackedPointerID === INVALID_POINTER_ID) { + this._trackedPointerID = pointerID; + this._initialPointerX = currentEvent.getX(); + this._initialPointerY = currentEvent.getY(); + + let args = _getPanArgs(0, 0, this._target, common.GestureStateTypes.began, initialEvent, currentEvent); + _executeCallback(this._observer, args); + } + + if (pointerID === this._trackedPointerID) { + this._deltaX = (currentEvent.getX() - this._initialPointerX) / this._density; + this._deltaY = (currentEvent.getY() - this._initialPointerY) / this._density; + + let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.changed, initialEvent, currentEvent); + _executeCallback(this._observer, args); + } return true; } public onTouchUpAction(currentEvent: android.view.MotionEvent) { - if (!this._isScrolling) { - return; + if (this._trackedPointerID !== INVALID_POINTER_ID) { + // Currently tracked pointer is up - emit end event. + let pointerID = currentEvent.getPointerId(currentEvent.getActionIndex()); + if (pointerID === this._trackedPointerID) { + this.stopTrackingCurrentPointer(currentEvent); + } } + } - var args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.ended, null, currentEvent); - _executeCallback(this._observer, args); - this._isScrolling = false; - this._deltaX = undefined; - this._deltaY = undefined; + private stopTrackingCurrentPointer(currentEvent: android.view.MotionEvent) { + if (this._trackedPointerID !== INVALID_POINTER_ID) { + this._deltaX = undefined; + this._deltaY = undefined; + this._trackedPointerID = INVALID_POINTER_ID; + + let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.ended, null, currentEvent); + _executeCallback(this._observer, args); + } } } @@ -444,8 +470,8 @@ class RotateGestureDetector { this.executeCallback(event, common.GestureStateTypes.began); break; case android.view.MotionEvent.ACTION_MOVE: - if (this.ptrID1 !==INVALID_POINTER_ID && this.ptrID2 !== INVALID_POINTER_ID) { - this.updateAngle(event); + if (this.ptrID1 !== INVALID_POINTER_ID && this.ptrID2 !== INVALID_POINTER_ID) { + this.updateAngle(event); this.executeCallback(event, common.GestureStateTypes.changed); } From e0681bcc409fd07b7d0666bd80e65de51bddfabc Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 18 Nov 2015 18:58:59 +0200 Subject: [PATCH 4/6] Android: Multi-touch handling in rotate gesture. --- ui/gestures/gestures.android.ts | 69 ++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/ui/gestures/gestures.android.ts b/ui/gestures/gestures.android.ts index 4fd6142b3..da4126304 100644 --- a/ui/gestures/gestures.android.ts +++ b/ui/gestures/gestures.android.ts @@ -443,52 +443,77 @@ class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureLis class RotateGestureDetector { private observer: GesturesObserver; private target: view.View; - private ptrID1: number; - private ptrID2: number; + private trackedPtrId1: number; + private trackedPtrId2: number; private initalPointersAngle: number; private angle: number; + private get isTracking(): boolean { + return this.trackedPtrId1 !== INVALID_POINTER_ID && this.trackedPtrId2 !== INVALID_POINTER_ID; + } + constructor(observer: GesturesObserver, target: view.View) { this.observer = observer; this.target = target; - this.ptrID1 = INVALID_POINTER_ID; - this.ptrID2 = INVALID_POINTER_ID; + this.trackedPtrId1 = INVALID_POINTER_ID; + this.trackedPtrId2 = INVALID_POINTER_ID; } public onTouchEvent(event: android.view.MotionEvent) { + let pointerID = event.getPointerId(event.getActionIndex()); + let wasTracking = this.isTracking; switch (event.getActionMasked()) { case android.view.MotionEvent.ACTION_DOWN: - this.ptrID1 = event.getPointerId(event.getActionIndex()); - break; case android.view.MotionEvent.ACTION_POINTER_DOWN: - this.ptrID2 = event.getPointerId(event.getActionIndex()); - this.initalPointersAngle = this.getPointersAngle(event); + let assigned = false; + if (this.trackedPtrId1 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId2) { + this.trackedPtrId1 = pointerID; + assigned = true; + } + else if (this.trackedPtrId2 === INVALID_POINTER_ID && pointerID !== this.trackedPtrId1) { + this.trackedPtrId2 = pointerID; + assigned = true; + } - this.executeCallback(event, common.GestureStateTypes.began); + if (assigned && this.isTracking) { + // We have started tracking 2 pointers + this.angle = 0; + this.initalPointersAngle = this.getPointersAngle(event); + this.executeCallback(event, common.GestureStateTypes.began); + } break; + case android.view.MotionEvent.ACTION_MOVE: - if (this.ptrID1 !== INVALID_POINTER_ID && this.ptrID2 !== INVALID_POINTER_ID) { + if (this.isTracking) { this.updateAngle(event); this.executeCallback(event, common.GestureStateTypes.changed); } break; + case android.view.MotionEvent.ACTION_UP: - this.ptrID1 = INVALID_POINTER_ID; - break; case android.view.MotionEvent.ACTION_POINTER_UP: - this.ptrID2 = INVALID_POINTER_ID; + if (pointerID === this.trackedPtrId1) { + this.trackedPtrId1 = INVALID_POINTER_ID; + } + else if (pointerID === this.trackedPtrId2) { + this.trackedPtrId2 = INVALID_POINTER_ID; + } - this.executeCallback(event, common.GestureStateTypes.ended); + if (wasTracking && !this.isTracking) { + this.executeCallback(event, common.GestureStateTypes.ended); + } break; - case android.view.MotionEvent.ACTION_CANCEL: - this.ptrID1 = INVALID_POINTER_ID; - this.ptrID2 = INVALID_POINTER_ID; - this.executeCallback(event, common.GestureStateTypes.cancelled); + case android.view.MotionEvent.ACTION_CANCEL: + this.trackedPtrId1 = INVALID_POINTER_ID; + this.trackedPtrId2 = INVALID_POINTER_ID; + if (wasTracking) { + this.executeCallback(event, common.GestureStateTypes.cancelled); + } break; } return true; @@ -524,10 +549,10 @@ class RotateGestureDetector { } private getPointersAngle(event: android.view.MotionEvent) { - let firstX = event.getX(event.findPointerIndex(this.ptrID1)); - let firstY = event.getY(event.findPointerIndex(this.ptrID1)); - let secondX = event.getX(event.findPointerIndex(this.ptrID2)); - let secondY = event.getY(event.findPointerIndex(this.ptrID2)); + let firstX = event.getX(event.findPointerIndex(this.trackedPtrId1)); + let firstY = event.getY(event.findPointerIndex(this.trackedPtrId1)); + let secondX = event.getX(event.findPointerIndex(this.trackedPtrId2)); + let secondY = event.getY(event.findPointerIndex(this.trackedPtrId2)); return Math.atan2((secondY - firstY), (secondX - firstX)); } From fe72a43e49cc318d7a4cc2488d66aaf9e07feab1 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Thu, 19 Nov 2015 12:11:51 +0200 Subject: [PATCH 5/6] IOS: Support simultaneous gesture recognizers. --- ui/gestures/gestures.ios.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/gestures/gestures.ios.ts b/ui/gestures/gestures.ios.ts index a78fa84bc..ecf120288 100644 --- a/ui/gestures/gestures.ios.ts +++ b/ui/gestures/gestures.ios.ts @@ -6,6 +6,14 @@ import trace = require("trace"); global.moduleMerge(common, exports); +class UIGestureRecognizerDelegateImpl extends NSObject implements UIGestureRecognizerDelegate { + public static ObjCProtocols = [UIGestureRecognizerDelegate]; + public gestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer): boolean { + return true; + } +} +var recognizerDelegateInstance: UIGestureRecognizerDelegateImpl = UIGestureRecognizerDelegateImpl.new(); + class UIGestureRecognizerImpl extends NSObject { private _owner: WeakRef; @@ -199,6 +207,7 @@ export class GesturesObserver extends common.GesturesObserver { } if (recognizer) { + recognizer.delegate = recognizerDelegateInstance; this._recognizers[name] = { recognizer: recognizer, target: target }; } } From 4094d916d6798a4d867910e4a1ca3bb720bfe9a4 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Fri, 20 Nov 2015 16:18:46 +0200 Subject: [PATCH 6/6] Custom pan detector --- ui/gestures/gestures.android.ts | 151 ++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 67 deletions(-) diff --git a/ui/gestures/gestures.android.ts b/ui/gestures/gestures.android.ts index da4126304..d2786ef6a 100644 --- a/ui/gestures/gestures.android.ts +++ b/ui/gestures/gestures.android.ts @@ -17,9 +17,8 @@ export class GesturesObserver extends common.GesturesObserver { private _simpleGestureDetector: android.view.GestureDetector; private _scaleGestureDetector: android.view.ScaleGestureDetector; private _swipeGestureDetector: android.view.GestureDetector; - private _panGestureDetector: android.view.GestureDetector - private _panGestureListener: PanGestureListener; - private _rotateGestureDetector: RotateGestureDetector; + private _panGestureDetector: CustomPanGestureDetector; + private _rotateGestureDetector: CustomRotateGestureDetector; private _onTargetLoaded: (data: observable.EventData) => void; private _onTargetUnloaded: (data: observable.EventData) => void; @@ -67,7 +66,6 @@ export class GesturesObserver extends common.GesturesObserver { this._scaleGestureDetector = null; this._swipeGestureDetector = null; this._panGestureDetector = null; - this._panGestureListener = null; this._rotateGestureDetector = null; } @@ -88,12 +86,11 @@ export class GesturesObserver extends common.GesturesObserver { } if (type & definition.GestureTypes.pan) { - this._panGestureListener = new PanGestureListener(this, this.target); - this._panGestureDetector = new android.support.v4.view.GestureDetectorCompat(target._context, this._panGestureListener); + this._panGestureDetector = new CustomPanGestureDetector(this, this.target); } if (type & definition.GestureTypes.rotation) { - this._rotateGestureDetector = new RotateGestureDetector(this, this.target); + this._rotateGestureDetector = new CustomRotateGestureDetector(this, this.target); } } @@ -112,11 +109,6 @@ export class GesturesObserver extends common.GesturesObserver { if (this._panGestureDetector) { this._panGestureDetector.onTouchEvent(motionEvent); - - if (motionEvent.getActionMasked() === android.view.MotionEvent.ACTION_UP || - motionEvent.getActionMasked() === android.view.MotionEvent.ACTION_POINTER_UP) { - this._panGestureListener.onTouchUpAction(motionEvent); - } } if (this._rotateGestureDetector) { @@ -233,7 +225,7 @@ class PinchGestureListener extends android.view.ScaleGestureDetector.SimpleOnSca this._observer = observer; this._target = target; - + return global.__native(this); } @@ -364,83 +356,108 @@ class SwipeGestureListener extends android.view.GestureDetector.SimpleOnGestureL } } -class PanGestureListener extends android.view.GestureDetector.SimpleOnGestureListener { - private _observer: GesturesObserver; - private _target: view.View; +class CustomPanGestureDetector { + private observer: GesturesObserver; + private target: view.View; - private _density: number; - private _deltaX: number; - private _deltaY: number; + private density: number; - private _trackedPointerID: number; - private _initialPointerX: number; - private _initialPointerY: number; + private isTracking: boolean; + private deltaX: number; + private deltaY: number; + private initialX: number; + private initialY: number; + + private lastEventCache: android.view.MotionEvent; constructor(observer: GesturesObserver, target: view.View) { - super(); + this.observer = observer; + this.target = target; + this.isTracking = false; - this._observer = observer; - this._target = target; - this._trackedPointerID = INVALID_POINTER_ID; - this._density = utils.layout.getDisplayDensity(); - - return global.__native(this); + this.density = utils.layout.getDisplayDensity(); } - public onDown(motionEvent: android.view.MotionEvent): boolean { - return false; - } + public onTouchEvent(event: android.view.MotionEvent) { + let pointerID = event.getPointerId(event.getActionIndex()); + let wasTracking = this.isTracking; - public onScroll(initialEvent: android.view.MotionEvent, currentEvent: android.view.MotionEvent, lastDeltaX: number, lastDeltaY: number): boolean { - let pointerID = currentEvent.getPointerId(currentEvent.getActionIndex()); + switch (event.getActionMasked()) { + case android.view.MotionEvent.ACTION_UP: + case android.view.MotionEvent.ACTION_CANCEL: + this.trackStop(event, false); + break; - if (this._trackedPointerID !== INVALID_POINTER_ID && this._trackedPointerID !== pointerID) { - // New pointer - stop tracking the old one. - this.stopTrackingCurrentPointer(currentEvent); - } + case android.view.MotionEvent.ACTION_DOWN: + case android.view.MotionEvent.ACTION_POINTER_DOWN: + case android.view.MotionEvent.ACTION_POINTER_UP: + this.trackStop(event, true); + break; - if (this._trackedPointerID === INVALID_POINTER_ID) { - this._trackedPointerID = pointerID; - this._initialPointerX = currentEvent.getX(); - this._initialPointerY = currentEvent.getY(); + case android.view.MotionEvent.ACTION_MOVE: + if (!this.isTracking) { + this.trackStart(event) + } - let args = _getPanArgs(0, 0, this._target, common.GestureStateTypes.began, initialEvent, currentEvent); - _executeCallback(this._observer, args); - } - - if (pointerID === this._trackedPointerID) { - this._deltaX = (currentEvent.getX() - this._initialPointerX) / this._density; - this._deltaY = (currentEvent.getY() - this._initialPointerY) / this._density; - - let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.changed, initialEvent, currentEvent); - _executeCallback(this._observer, args); + this.trackChange(event); + break; } return true; } - public onTouchUpAction(currentEvent: android.view.MotionEvent) { - if (this._trackedPointerID !== INVALID_POINTER_ID) { - // Currently tracked pointer is up - emit end event. - let pointerID = currentEvent.getPointerId(currentEvent.getActionIndex()); - if (pointerID === this._trackedPointerID) { - this.stopTrackingCurrentPointer(currentEvent); - } + private trackStop(currentEvent: android.view.MotionEvent, cahceEvent: boolean) { + if (this.isTracking) { + let args = _getPanArgs(this.deltaX, this.deltaY, this.target, common.GestureStateTypes.ended, null, currentEvent); + _executeCallback(this.observer, args); + + this.deltaX = undefined; + this.deltaY = undefined; + this.isTracking = false; + } + + if (cahceEvent) { + this.lastEventCache = currentEvent; + } + else { + this.lastEventCache = undefined; } } - private stopTrackingCurrentPointer(currentEvent: android.view.MotionEvent) { - if (this._trackedPointerID !== INVALID_POINTER_ID) { - this._deltaX = undefined; - this._deltaY = undefined; - this._trackedPointerID = INVALID_POINTER_ID; + private trackStart(currentEvent: android.view.MotionEvent) { + let inital = this.getMotionEventCenter(this.lastEventCache ? this.lastEventCache : currentEvent); + this.initialX = inital.x; + this.initialY = inital.y; + this.isTracking = true; - let args = _getPanArgs(this._deltaX, this._deltaY, this._target, common.GestureStateTypes.ended, null, currentEvent); - _executeCallback(this._observer, args); + let args = _getPanArgs(0, 0, this.target, common.GestureStateTypes.began, null, currentEvent); + _executeCallback(this.observer, args); + } + + private trackChange(currentEvent: android.view.MotionEvent) { + let current = this.getMotionEventCenter(currentEvent); + this.deltaX = current.x - this.initialX; + this.deltaY = current.y - this.initialY; + + let args = _getPanArgs(this.deltaX, this.deltaY, this.target, common.GestureStateTypes.changed, null, currentEvent); + _executeCallback(this.observer, args); + } + + private getMotionEventCenter(event: android.view.MotionEvent): { x: number, y: number } { + let count = event.getPointerCount(); + let res = { x: 0, y: 0 }; + for (var i = 0; i < count; i++) { + res.x += event.getX(i); + res.y += event.getY(i); } + + res.x /= (count * this.density); + res.y /= (count * this.density); + + return res; } } -class RotateGestureDetector { +class CustomRotateGestureDetector { private observer: GesturesObserver; private target: view.View; private trackedPtrId1: number;