NativeScript 手势处理 #
手势概述 #
NativeScript 提供了丰富的手势识别系统,支持各种触摸交互。
text
┌─────────────────────────────────────────────────────────────┐
│ 手势类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 基本手势 │
│ ├── tap 点击 │
│ ├── doubleTap 双击 │
│ ├── longPress 长按 │
│ └── touch 触摸事件 │
│ │
│ 滑动手势 │
│ ├── swipe 滑动 │
│ ├── pan 拖拽 │
│ └── pinch 捏合 │
│ │
│ 复合手势 │
│ ├── rotation 旋转 │
│ └── 组合手势 │
│ │
└─────────────────────────────────────────────────────────────┘
基本手势 #
点击手势 (tap) #
xml
<!-- XML 中绑定 -->
<Button text="Click Me" tap="onTap" />
<Image src="~/image.png" tap="onImageTap" />
typescript
// TypeScript 处理
export function onTap(args: EventData) {
const button = args.object as Button;
console.log('Button tapped');
}
双击手势 (doubleTap) #
xml
<Label text="Double Tap Me" doubleTap="onDoubleTap" />
typescript
export function onDoubleTap(args: EventData) {
console.log('Double tapped');
}
长按手势 (longPress) #
xml
<Button text="Long Press Me" longPress="onLongPress" />
typescript
export function onLongPress(args: EventData) {
console.log('Long pressed');
}
触摸事件 (touch) #
xml
<GridLayout touch="onTouch">
<Label text="Touch Area" />
</GridLayout>
typescript
import { TouchGestureEventData } from '@nativescript/core';
export function onTouch(args: TouchGestureEventData) {
const action = args.action;
switch (action) {
case 'down':
console.log('Touch started');
break;
case 'move':
console.log('Touch moved', args.getX(), args.getY());
break;
case 'up':
console.log('Touch ended');
break;
}
}
滑动手势 #
滑动手势 (swipe) #
xml
<GridLayout swipe="onSwipe" class="swipe-area">
<Label text="Swipe Me" />
</GridLayout>
typescript
import { SwipeGestureEventData } from '@nativescript/core';
export function onSwipe(args: SwipeGestureEventData) {
const direction = args.direction;
switch (direction) {
case SwipeDirection.left:
console.log('Swiped left');
break;
case SwipeDirection.right:
console.log('Swiped right');
break;
case SwipeDirection.up:
console.log('Swiped up');
break;
case SwipeDirection.down:
console.log('Swiped down');
break;
}
}
拖拽手势 (pan) #
xml
<GridLayout pan="onPan" class="pan-area">
<Label text="Drag Me" id="draggable" />
</GridLayout>
typescript
import { PanGestureEventData } from '@nativescript/core';
let startX = 0;
let startY = 0;
export function onPan(args: PanGestureEventData) {
const view = args.object as View;
switch (args.state) {
case GestureStateTypes.began:
startX = view.translateX;
startY = view.translateY;
break;
case GestureStateTypes.changed:
view.translateX = startX + args.deltaX;
view.translateY = startY + args.deltaY;
break;
case GestureStateTypes.ended:
console.log('Pan ended');
break;
}
}
捏合手势 (pinch) #
xml
<Image src="~/image.png" pinch="onPinch" id="pinchable" />
typescript
import { PinchGestureEventData } from '@nativescript/core';
let initialScale = 1;
export function onPinch(args: PinchGestureEventData) {
const view = args.object as View;
switch (args.state) {
case GestureStateTypes.began:
initialScale = view.scaleX;
break;
case GestureStateTypes.changed:
const newScale = initialScale * args.scale;
view.scaleX = newScale;
view.scaleY = newScale;
break;
}
}
旋转手势 (rotation) #
xml
<Image src="~/image.png" rotation="onRotation" id="rotatable" />
typescript
import { RotationGestureEventData } from '@nativescript/core';
let initialRotation = 0;
export function onRotation(args: RotationGestureEventData) {
const view = args.object as View;
switch (args.state) {
case GestureStateTypes.began:
initialRotation = view.rotation;
break;
case GestureStateTypes.changed:
view.rotation = initialRotation + args.rotation;
break;
}
}
使用 Gestures 模块 #
编程式添加手势 #
typescript
import { Gestures, GestureTypes, GestureEventData } from '@nativescript/core';
const view = page.getViewById('myView');
// 添加点击手势
view.on(GestureTypes.tap, (args: GestureEventData) => {
console.log('Tapped');
});
// 添加滑动手势
view.on(GestureTypes.swipe, (args: SwipeGestureEventData) => {
console.log('Swiped:', args.direction);
});
// 添加多个手势
view.on(GestureTypes.tap | GestureTypes.doubleTap, (args) => {
console.log('Gesture event');
});
// 移除手势
view.off(GestureTypes.tap);
手势观察者 #
typescript
import { GestureTypes, observe } from '@nativescript/core';
const observer = observe(view, GestureTypes.tap, (args) => {
console.log('Tap observed');
});
// 取消观察
observer.disconnect();
手势服务 #
typescript
// services/gesture.service.ts
import { Injectable } from '@angular/core';
import {
View,
GestureTypes,
GestureEventData,
SwipeGestureEventData,
PanGestureEventData,
PinchGestureEventData
} from '@nativescript/core';
@Injectable({
providedIn: 'root'
})
export class GestureService {
onTap(view: View, callback: (args: GestureEventData) => void): void {
view.on(GestureTypes.tap, callback);
}
onDoubleTap(view: View, callback: (args: GestureEventData) => void): void {
view.on(GestureTypes.doubleTap, callback);
}
onLongPress(view: View, callback: (args: GestureEventData) => void): void {
view.on(GestureTypes.longPress, callback);
}
onSwipe(view: View, callback: (args: SwipeGestureEventData) => void): void {
view.on(GestureTypes.swipe, callback);
}
onPan(view: View, callback: (args: PanGestureEventData) => void): void {
view.on(GestureTypes.pan, callback);
}
onPinch(view: View, callback: (args: PinchGestureEventData) => void): void {
view.on(GestureTypes.pinch, callback);
}
enableDraggable(view: View, bounds?: { minX: number, maxX: number, minY: number, maxY: number }): void {
let startX = 0;
let startY = 0;
view.on(GestureTypes.pan, (args: PanGestureEventData) => {
switch (args.state) {
case GestureStateTypes.began:
startX = view.translateX;
startY = view.translateY;
break;
case GestureStateTypes.changed:
let newX = startX + args.deltaX;
let newY = startY + args.deltaY;
if (bounds) {
newX = Math.max(bounds.minX, Math.min(bounds.maxX, newX));
newY = Math.max(bounds.minY, Math.min(bounds.maxY, newY));
}
view.translateX = newX;
view.translateY = newY;
break;
}
});
}
enablePinchZoom(view: View, minScale: number = 0.5, maxScale: number = 3): void {
let initialScale = 1;
view.on(GestureTypes.pinch, (args: PinchGestureEventData) => {
switch (args.state) {
case GestureStateTypes.began:
initialScale = view.scaleX;
break;
case GestureStateTypes.changed:
const newScale = Math.max(minScale, Math.min(maxScale, initialScale * args.scale));
view.scaleX = newScale;
view.scaleY = newScale;
break;
}
});
}
enableSwipeNavigation(view: View, callbacks: {
onLeft?: () => void,
onRight?: () => void,
onUp?: () => void,
onDown?: () => void
}): void {
view.on(GestureTypes.swipe, (args: SwipeGestureEventData) => {
switch (args.direction) {
case SwipeDirection.left:
callbacks.onLeft?.();
break;
case SwipeDirection.right:
callbacks.onRight?.();
break;
case SwipeDirection.up:
callbacks.onUp?.();
break;
case SwipeDirection.down:
callbacks.onDown?.();
break;
}
});
}
}
实用示例 #
可拖拽视图 #
typescript
export class DraggableView {
private startX = 0;
private startY = 0;
constructor(private view: View) {
this.setupDrag();
}
private setupDrag(): void {
this.view.on(GestureTypes.pan, (args: PanGestureEventData) => {
switch (args.state) {
case GestureStateTypes.began:
this.startX = this.view.translateX;
this.startY = this.view.translateY;
this.view.animate({ scale: { x: 1.1, y: 1.1 }, duration: 100 });
break;
case GestureStateTypes.changed:
this.view.translateX = this.startX + args.deltaX;
this.view.translateY = this.startY + args.deltaY;
break;
case GestureStateTypes.ended:
this.view.animate({ scale: { x: 1, y: 1 }, duration: 100 });
break;
}
});
}
}
双指缩放图片 #
typescript
export class ZoomableImageView {
private initialScale = 1;
private minScale = 0.5;
private maxScale = 3;
constructor(private view: View) {
this.setupZoom();
}
private setupZoom(): void {
this.view.on(GestureTypes.pinch, (args: PinchGestureEventData) => {
switch (args.state) {
case GestureStateTypes.began:
this.initialScale = this.view.scaleX;
break;
case GestureStateTypes.changed:
const newScale = Math.max(
this.minScale,
Math.min(this.maxScale, this.initialScale * args.scale)
);
this.view.scaleX = newScale;
this.view.scaleY = newScale;
break;
}
});
// 双击重置
this.view.on(GestureTypes.doubleTap, () => {
this.view.animate({
scale: { x: 1, y: 1 },
duration: 200
});
});
}
}
滑动切换页面 #
typescript
export class SwipeNavigator {
constructor(
private view: View,
private pages: string[],
private currentIndex: number = 0
) {
this.setupSwipe();
}
private setupSwipe(): void {
this.view.on(GestureTypes.swipe, (args: SwipeGestureEventData) => {
if (args.direction === SwipeDirection.left) {
this.nextPage();
} else if (args.direction === SwipeDirection.right) {
this.prevPage();
}
});
}
private nextPage(): void {
if (this.currentIndex < this.pages.length - 1) {
this.currentIndex++;
this.navigate();
}
}
private prevPage(): void {
if (this.currentIndex > 0) {
this.currentIndex--;
this.navigate();
}
}
private navigate(): void {
Frame.topmost().navigate({
moduleName: this.pages[this.currentIndex],
transition: { name: 'slide' }
});
}
}
下拉刷新 #
typescript
export class PullToRefresh {
private startY = 0;
private threshold = 100;
private isRefreshing = false;
constructor(
private view: View,
private onRefresh: () => Promise<void>
) {
this.setupPullToRefresh();
}
private setupPullToRefresh(): void {
this.view.on(GestureTypes.pan, (args: PanGestureEventData) => {
if (this.isRefreshing) return;
switch (args.state) {
case GestureStateTypes.began:
this.startY = this.view.translateY;
break;
case GestureStateTypes.changed:
if (args.deltaY > 0) {
this.view.translateY = Math.min(args.deltaY * 0.5, this.threshold);
}
break;
case GestureStateTypes.ended:
if (this.view.translateY >= this.threshold) {
this.triggerRefresh();
} else {
this.resetPosition();
}
break;
}
});
}
private async triggerRefresh(): Promise<void> {
this.isRefreshing = true;
try {
await this.onRefresh();
} finally {
this.isRefreshing = false;
this.resetPosition();
}
}
private resetPosition(): void {
this.view.animate({
translate: { x: 0, y: 0 },
duration: 200
});
}
}
手势冲突处理 #
同时识别多个手势 #
typescript
view.on(GestureTypes.tap | GestureTypes.doubleTap, (args) => {
if (args.type === GestureTypes.tap) {
// 处理单击
} else if (args.type === GestureTypes.doubleTap) {
// 处理双击
}
});
手势优先级 #
typescript
// 使用延迟处理单击,避免与双击冲突
let tapTimeout: number;
view.on(GestureTypes.tap, () => {
tapTimeout = setTimeout(() => {
console.log('Single tap');
}, 300);
});
view.on(GestureTypes.doubleTap, () => {
clearTimeout(tapTimeout);
console.log('Double tap');
});
最佳实践 #
手势设计原则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 手势设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 直观性 │
│ 手势应该直观易懂 │
│ │
│ 2. 一致性 │
│ 与系统手势保持一致 │
│ │
│ 3. 反馈 │
│ 提供视觉或触觉反馈 │
│ │
│ 4. 容错性 │
│ 允许用户取消手势操作 │
│ │
│ 5. 可发现性 │
│ 提供手势提示或教程 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
现在你已经掌握了手势处理,接下来学习 性能优化,了解如何优化应用性能!
最后更新:2026-03-29