Merge pull request #1383 from NativeScript/feature/touch-gesture

Touch gesture implemented
This commit is contained in:
Alexander Vakrilov
2016-01-18 14:35:03 +02:00
10 changed files with 416 additions and 31 deletions

View File

@@ -229,6 +229,10 @@
<Content Include="apps\ui-tests-app\layouts-percent\wrap.xml">
<SubType>Designer</SubType>
</Content>
<TypeScriptCompile Include="apps\ui-tests-app\pages\touch-event.ts" />
<Content Include="apps\ui-tests-app\pages\touch-event.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="apps\ui-tests-app\segmented-bar\all.xml">
<SubType>Designer</SubType>
</Content>

View File

@@ -79,6 +79,7 @@ examples.set("modalview", "modal-view/modal-view");
examples.set("nordic", "nordic/nordic");
examples.set("gestures", "pages/gestures");
examples.set("touch", "pages/touch-event");
examples.set("handlers", "pages/handlers");
//examples.set("listview_binding", "pages/listview_binding");
examples.set("console", "pages/console");

View File

@@ -0,0 +1,30 @@
import { Page, View, TextView } from "ui";
import gestures = require("ui/gestures");
export function onTouch(args: gestures.TouchGestureEventData) {
var msg = " touch action: " + args.action +
" x: " + Math.round(args.getX()) + " y: " + Math.round(args.getY()) +
" count: " + args.getPointerCount();
var p;
msg += " ACTIVE: ";
var pointers = args.getActivePointers();
for (var index = 0; index < pointers.length; index++) {
p = pointers[index];
msg += " p" + index + "[" + Math.round(p.getX()) + ", " + Math.round(p.getY()) + "]"
}
msg += " ALL: ";
pointers = args.getAllPointers();
for (var index = 0; index < pointers.length; index++) {
p = pointers[index];
msg += " p" + index + "[" + Math.round(p.getX()) + ", " + Math.round(p.getY()) + "]"
}
console.log(msg);
(<TextView>args.view.page.getViewById("output")).text += msg + "\n";
}
export function clear(args) {
args.object.page.getViewById("output").text = "";
}

View File

@@ -0,0 +1,7 @@
<Page>
<GridLayout rows="*, auto, *" >
<GridLayout backgroundColor="lightgreen" id="target" margin="20" touch="onTouch"/>
<Button row="1" text="clear" tap="clear"/>
<TextView row="2" text="here" id="output" style="font-size: 10, font-family: monospace;"/>
</GridLayout>
</Page>

View File

@@ -373,6 +373,7 @@
"apps/ui-tests-app/pages/i61.ts",
"apps/ui-tests-app/pages/i73.ts",
"apps/ui-tests-app/pages/listview_binding.ts",
"apps/ui-tests-app/pages/touch-event.ts",
"apps/ui-tests-app/segmented-bar/all.ts",
"apps/ui-tests-app/segmented-bar/clean.ts",
"apps/ui-tests-app/text-field/text-field.ts",

View File

@@ -79,7 +79,7 @@ function onIsUserInteractionEnabledPropertyChanged(data: dependencyObservable.Pr
export class View extends viewCommon.View {
private _disableUserInteractionListener: android.view.View.OnTouchListener = new android.view.View.OnTouchListener({
onTouch: function (view: android.view.View, event: android.view.MotionEvent) {
onTouch: function(view: android.view.View, event: android.view.MotionEvent) {
return true;
}
});
@@ -141,7 +141,7 @@ export class View extends viewCommon.View {
this._nativeView.setClickable(true);
}
this._nativeView.setOnTouchListener(new android.view.View.OnTouchListener({
onTouch: function (view: android.view.View, motionEvent: android.view.MotionEvent) {
onTouch: function(view: android.view.View, motionEvent: android.view.MotionEvent) {
var owner = that.get();
if (!owner) {
return false;
@@ -201,7 +201,7 @@ export class View extends viewCommon.View {
if (this._childrenCount > 0) {
// Notify each child for the _onAttached event
var that = this;
var eachChild = function (child: View): boolean {
var eachChild = function(child: View): boolean {
child._onAttached(context);
if (!child._isAddedToNativeVisualTree) {
// since we have lazy loading of the android widgets, we need to add the native instances at this point.
@@ -217,7 +217,7 @@ export class View extends viewCommon.View {
if (this._childrenCount > 0) {
// Detach children first
var that = this;
var eachChild = function (child: View): boolean {
var eachChild = function(child: View): boolean {
if (child._isAddedToNativeVisualTree) {
that._removeViewFromNativeVisualTree(child);
}
@@ -397,7 +397,7 @@ export class CustomLayoutView extends View implements viewDefinition.CustomLayou
super._addViewToNativeVisualTree(child);
if (this._nativeView && child._nativeView) {
var types : typeof typesModule = require("utils/types");
var types: typeof typesModule = require("utils/types");
if (types.isNullOrUndefined(atIndex) || atIndex >= this._nativeView.getChildCount()) {
this._nativeView.addView(child._nativeView);

View File

@@ -8,7 +8,8 @@ export enum GestureTypes {
pan = 1 << 3,
swipe = 1 << 4,
rotation = 1 << 5,
longPress = 1 << 6
longPress = 1 << 6,
touch = 1 << 7
}
export enum GestureStateTypes {
@@ -25,8 +26,15 @@ export enum SwipeDirection {
down = 1 << 3
}
export function observe(target: view.View, type: definition.GestureTypes, callback: (args: definition.GestureEventData) => void, thisArg?: any): definition.GesturesObserver {
var observer = new definition.GesturesObserver(target, callback, thisArg);
export module TouchAction {
export var down = "down";
export var up = "up";
export var move = "move";
export var cancel = "cancel";
}
export function observe(target: view.View, type: definition.GestureTypes, callback: (args: definition.GestureEventData) => void, context?: any): definition.GesturesObserver {
var observer = new definition.GesturesObserver(target, callback, context);
observer.observe(type);
return observer;
}
@@ -62,6 +70,10 @@ export function toString(type: GestureTypes, separator?: string): string {
types.push("longPress");
}
if (type & definition.GestureTypes.touch) {
types.push("touch");
}
return types.join(separator);
}
@@ -82,6 +94,8 @@ export function fromString(type: string): definition.GestureTypes {
return definition.GestureTypes.rotation;
} else if (t === "longpress") {
return definition.GestureTypes.longPress;
} else if (t === "touch") {
return definition.GestureTypes.touch;
}
return undefined;

View File

@@ -4,6 +4,7 @@ import observable = require("data/observable");
import view = require("ui/core/view");
import trace = require("trace");
import utils = require("utils/utils");
import types = require("utils/types");
global.moduleMerge(common, exports);
@@ -13,13 +14,15 @@ const INVALID_POINTER_ID = -1;
const TO_DEGREES = (180 / Math.PI);
export class GesturesObserver extends common.GesturesObserver {
private _onTouchListener: android.view.View.OnTouchListener;
private _notifyTouch: boolean;
private _simpleGestureDetector: android.view.GestureDetector;
private _scaleGestureDetector: android.view.ScaleGestureDetector;
private _swipeGestureDetector: android.view.GestureDetector;
private _panGestureDetector: CustomPanGestureDetector;
private _rotateGestureDetector: CustomRotateGestureDetector;
private _eventData: TouchGestureEventData;
private _onTargetLoaded: (data: observable.EventData) => void;
private _onTargetUnloaded: (data: observable.EventData) => void;
@@ -61,12 +64,13 @@ export class GesturesObserver extends common.GesturesObserver {
private _detach() {
trace.write(this.target + "._detach() android:" + this.target._nativeView, "gestures");
this._onTouchListener = null;
this._notifyTouch = false
this._simpleGestureDetector = null;
this._scaleGestureDetector = null;
this._swipeGestureDetector = null;
this._panGestureDetector = null;
this._rotateGestureDetector = null;
this._eventData = null;
}
private _attach(target: view.View, type: definition.GestureTypes) {
@@ -92,9 +96,22 @@ export class GesturesObserver extends common.GesturesObserver {
if (type & definition.GestureTypes.rotation) {
this._rotateGestureDetector = new CustomRotateGestureDetector(this, this.target);
}
if (type & definition.GestureTypes.touch) {
this._notifyTouch = true;
}
}
public androidOnTouchEvent(motionEvent: android.view.MotionEvent) {
if (this._notifyTouch) {
if (!this._eventData) {
this._eventData = new TouchGestureEventData();
}
this._eventData.prepare(this.target, motionEvent);
_executeCallback(this, this._eventData);
}
if (this._simpleGestureDetector) {
this._simpleGestureDetector.onTouchEvent(motionEvent);
}
@@ -117,18 +134,6 @@ export class GesturesObserver extends common.GesturesObserver {
}
}
function getState(e: android.view.MotionEvent): common.GestureStateTypes {
if (e.getActionMasked() === android.view.MotionEvent.ACTION_DOWN) {
return common.GestureStateTypes.began;
} else if (e.getActionMasked() === android.view.MotionEvent.ACTION_CANCEL) {
return common.GestureStateTypes.cancelled;
} else if (e.getActionMasked() === android.view.MotionEvent.ACTION_MOVE) {
return common.GestureStateTypes.changed;
} else if (e.getActionMasked() === android.view.MotionEvent.ACTION_UP) {
return common.GestureStateTypes.ended;
}
}
function _getArgs(type: definition.GestureTypes, view: view.View, e: android.view.MotionEvent): definition.GestureEventData {
return <definition.GestureEventData>{
type: type,
@@ -586,4 +591,95 @@ class CustomRotateGestureDetector {
return Math.atan2((secondY - firstY), (secondX - firstX));
}
}
class Pointer implements definition.Pointer {
public android: number;
public ios: any = undefined;
constructor(id: number, private event: android.view.MotionEvent) {
this.android = id;
}
getX(): number {
return this.event.getX(this.android) / utils.layout.getDisplayDensity();
}
getY(): number {
return this.event.getY(this.android) / utils.layout.getDisplayDensity();
}
}
class TouchGestureEventData implements definition.TouchGestureEventData {
eventName: string = definition.toString(definition.GestureTypes.touch);
type: definition.GestureTypes = definition.GestureTypes.touch;
ios: any = undefined;
action: string;
view: view.View;
android: android.view.MotionEvent;
object: any;
private _activePointers: Array<Pointer>;
private _allPointers: Array<Pointer>;
public prepare(view: view.View, e: android.view.MotionEvent) {
this.view = view;
this.object = view;
this.android = e;
this.action = this.getActionType(e);
this._activePointers = undefined;
this._allPointers = undefined;
}
getPointerCount(): number {
return this.android.getPointerCount();
}
getActivePointers(): Array<Pointer> {
// Only one active pointer in Android
if (!this._activePointers) {
this._activePointers = [new Pointer(this.android.getActionIndex(), this.android)];
}
return this._activePointers;
}
getAllPointers(): Array<Pointer> {
if (!this._allPointers) {
this._allPointers = [];
for (var i = 0; i < this.getPointerCount(); i++) {
this._allPointers.push(new Pointer(i, this.android));
}
}
return this._allPointers;
}
getX(): number {
return this.getActivePointers()[0].getX();
}
getY(): number {
return this.getActivePointers()[0].getY();
}
private getActionType(e: android.view.MotionEvent): string {
switch (e.getActionMasked()) {
case android.view.MotionEvent.ACTION_DOWN:
case android.view.MotionEvent.ACTION_POINTER_DOWN:
return common.TouchAction.down;
case android.view.MotionEvent.ACTION_MOVE:
return common.TouchAction.move;
case android.view.MotionEvent.ACTION_UP:
case android.view.MotionEvent.ACTION_POINTER_UP:
return common.TouchAction.up;
case android.view.MotionEvent.ACTION_CANCEL:
return common.TouchAction.cancel;
}
return "";
}
}

View File

@@ -36,7 +36,11 @@ declare module "ui/gestures" {
/**
* Denotes long press gesture.
*/
longPress
longPress,
/**
* Denotes touch action.
*/
touch
}
/**
@@ -44,7 +48,7 @@ declare module "ui/gestures" {
*/
export enum GestureStateTypes {
/**
* Gesture cancelled.
* Gesture canceled.
*/
cancelled,
/**
@@ -83,6 +87,31 @@ declare module "ui/gestures" {
down
}
/**
* Defines a touch action
*/
export module TouchAction {
/**
* Down action.
*/
export var down: string;
/**
* Up action.
*/
export var up: string;
/**
* Move action.
*/
export var move: string;
/**
* Cancel action.
*/
export var cancel: string;
}
/**
* Provides gesture event data.
*/
@@ -105,6 +134,67 @@ declare module "ui/gestures" {
android: any
}
/**
* Provides gesture event data.
*/
export interface TouchGestureEventData extends GestureEventData {
/**
* Gets action of the touch. Possible values: 'up', 'move', 'down', 'cancel'
*/
action: string;
/**
* Gets the X coordinate of this event inside the view that triggered the event.
*/
getX(): number;
/**
* Gets the Y coordinate of this event inside the view that triggered the event.
*/
getY(): number;
/**
* Gets the number of pointers in the event.
*/
getPointerCount(): number;
/**
* Gets the pointers that triggered the event.
* Note: In Android there is aways only one active pointer.
*/
getActivePointers(): Array<Pointer>;
/**
* Gets all pointers.
*/
getAllPointers(): Array<Pointer>;
}
/**
* Pointer is an object representing a finger (or other object) that is touching the screen.
*/
export interface Pointer {
/**
* The id of the pointer.
*/
android: any;
/**
* The UITouch object associated to the touch
*/
ios: any;
/**
* Gets the X coordinate of the pointer inside the view that triggered the event.
*/
getX(): number;
/**
* Gets the Y coordinate of the pointer inside the view that triggered the event.
*/
getY(): number;
}
/**
* Provides gesture event data for pinch gesture.
*/
@@ -150,7 +240,9 @@ declare module "ui/gestures" {
export class GesturesObserver {
/**
* Creates an instance of GesturesObserver class.
* @param target - The view for which the observer is created.
* @param callback - A function that will be executed when a gesture is received.
* @param context - default this argument for the callbacks.
*/
constructor(target: view.View, callback: (args: GestureEventData) => void, context: any);
@@ -191,8 +283,9 @@ declare module "ui/gestures" {
* @param target - View which will be watched for originating a specific gesture.
* @param type - Type of the gesture.
* @param callback - A function that will be executed when a gesture is received.
* @param context - this argument for the callback.
*/
export function observe(target: view.View, type: GestureTypes, callback: (args: GestureEventData) => void, thisArg?: any): GesturesObserver;
export function observe(target: view.View, type: GestureTypes, callback: (args: GestureEventData) => void, context?: any): GesturesObserver;
/**
* Returns a string representation of a gesture type.

View File

@@ -3,6 +3,7 @@ import definition = require("ui/gestures");
import view = require("ui/core/view");
import observable = require("data/observable");
import trace = require("trace");
import types = require("utils/types");
global.moduleMerge(common, exports);
@@ -151,6 +152,10 @@ export class GesturesObserver extends common.GesturesObserver {
if (type & definition.GestureTypes.longPress) {
nativeView.addGestureRecognizer(this._createRecognizer(definition.GestureTypes.longPress));
}
if (type & definition.GestureTypes.touch) {
nativeView.addGestureRecognizer(this._createRecognizer(definition.GestureTypes.touch));
}
}
}
@@ -184,7 +189,7 @@ export class GesturesObserver extends common.GesturesObserver {
super.disconnect();
}
private _executeCallback(args: definition.GestureEventData) {
public _executeCallback(args: definition.GestureEventData) {
if (this.callback) {
this.callback.call(this.context, args);
}
@@ -197,13 +202,14 @@ export class GesturesObserver extends common.GesturesObserver {
var recognizerType = _getUIGestureRecognizerType(type);
if (recognizerType) {
recognizer = recognizerType.alloc().initWithTargetAction(target, "recognize");
if (type === definition.GestureTypes.swipe && swipeDirection) {
name = name + swipeDirection.toString();
recognizer = recognizerType.alloc().initWithTargetAction(target, "recognize");
(<UISwipeGestureRecognizer>recognizer).direction = swipeDirection;
}
else {
recognizer = recognizerType.alloc().initWithTargetAction(target, "recognize");
else if (type === definition.GestureTypes.touch) {
(<TouchGestureRecognizer>recognizer).observer = this;
}
if (recognizer) {
@@ -216,8 +222,8 @@ export class GesturesObserver extends common.GesturesObserver {
}
}
function _createUIGestureRecognizerTarget(owner: GesturesObserver, type: definition.GestureTypes, callback?: (args: definition.GestureEventData) => void, thisArg?: any): any {
return UIGestureRecognizerImpl.initWithOwnerTypeCallback(new WeakRef(owner), type, callback, thisArg);
function _createUIGestureRecognizerTarget(owner: GesturesObserver, type: definition.GestureTypes, callback?: (args: definition.GestureEventData) => void, context?: any): any {
return UIGestureRecognizerImpl.initWithOwnerTypeCallback(new WeakRef(owner), type, callback, context);
}
interface RecognizerCache {
@@ -242,6 +248,8 @@ function _getUIGestureRecognizerType(type: definition.GestureTypes): any {
nativeType = UIRotationGestureRecognizer;
} else if (type === definition.GestureTypes.longPress) {
nativeType = UILongPressGestureRecognizer;
} else if (type === definition.GestureTypes.touch) {
nativeType = TouchGestureRecognizer;
}
return nativeType;
@@ -331,3 +339,134 @@ function _getRotationData(args: definition.GestureEventData): definition.Rotatio
state: getState(recognizer)
};
}
class TouchGestureRecognizer extends UIGestureRecognizer {
public observer: GesturesObserver;
private _eventData: TouchGestureEventData;
touchesBeganWithEvent(touches: NSSet, event: any): void {
this.executeCallback(common.TouchAction.down, touches, event);
}
touchesMovedWithEvent(touches: NSSet, event: any): void {
this.executeCallback(common.TouchAction.move, touches, event);
}
touchesEndedWithEvent(touches: NSSet, event: any): void {
this.executeCallback(common.TouchAction.up, touches, event);
}
touchesCancelledWithEvent(touches: NSSet, event: any): void {
this.executeCallback(common.TouchAction.cancel, touches, event);
}
private executeCallback(action: string, touches: NSSet, event: any): void {
if (!this._eventData) {
this._eventData = new TouchGestureEventData();
}
this._eventData.prepare(this.observer.target, action, touches, event);
this.observer._executeCallback(this._eventData);
}
}
class Pointer implements definition.Pointer {
public android: any = undefined;
public ios: UITouch = undefined;
private _view: view.View;
private _location: CGPoint;
private get location(): CGPoint {
if (!this._location) {
this._location = this.ios.locationInView(this._view._nativeView);
}
return this._location;
}
constructor(touch: UITouch, targetView: view.View) {
this.ios = touch;
this._view = targetView;
}
getX(): number {
return this.location.x;
}
getY(): number {
return this.location.x;
}
}
class TouchGestureEventData implements definition.TouchGestureEventData {
eventName: string = definition.toString(definition.GestureTypes.touch);
type: definition.GestureTypes = definition.GestureTypes.touch;
android: any = undefined;
action: string;
view: view.View;
ios: { touches: NSSet, event: { allTouches: () => NSSet } };
object: any;
private _activePointers: Array<Pointer>;
private _allPointers: Array<Pointer>;
private _mainPointer: UITouch;
public prepare(view: view.View, action: string, touches: NSSet, event: any) {
this.action = action;
this.view = view;
this.object = view;
this.ios = {
touches: touches,
event: event
};
this._mainPointer = undefined;
this._activePointers = undefined;
this._allPointers = undefined;
}
getPointerCount(): number {
return this.ios.event.allTouches().count;
}
private getMainPointer(): UITouch {
if (types.isUndefined(this._mainPointer)) {
this._mainPointer = this.ios.touches.anyObject();
}
return this._mainPointer;
}
getActivePointers(): Array<Pointer> {
if (!this._activePointers) {
this._activePointers = [];
for (let i = 0, nsArr = this.ios.touches.allObjects; i < nsArr.count; i++) {
this._activePointers.push(new Pointer(nsArr.objectAtIndex(i), this.view));
}
}
return this._activePointers;
}
getAllPointers(): Array<Pointer> {
if (!this._allPointers) {
this._allPointers = [];
let nsArr = this.ios.event.allTouches().allObjects;
for (var i = 0; i < nsArr.count; i++) {
this._allPointers.push(new Pointer(nsArr.objectAtIndex(i), this.view));
}
}
return this._allPointers;
}
getX(): number {
return this.getMainPointer().locationInView(this.view._nativeView).x;
}
getY(): number {
return this.getMainPointer().locationInView(this.view._nativeView).y
}
}