HarmonyOS鸿蒙Next中如何实现应用内手势方向+速度组合识别?
HarmonyOS鸿蒙Next中如何实现应用内手势方向+速度组合识别? **问题描述:**需要根据用户的手势方向(如左右滑动)和滑动速度来区分不同的操作(如快速右滑代表删除,慢速右滑代表标记)。
详细回答:
通过监听触摸事件并计算手指移动的方向和速度,可以实现手势方向与速度的组合识别。首先需要获取触摸点的位置变化以判断滑动方向,然后根据时间差计算出滑动速度。
支持手势识别的列表项组件
import { SwipeGestureRecognizer, SwipeInfo, SwipeDirection, SwipeSpeed } from './SwipeGestureRecognizer';
/**
* @author J.query
* @date 2025/12/26
* @email j-query@foxmail.com
* Description: 支持手势识别的列表项组件
*/
interface ListItemData {
id: string;
title: string;
subtitle?: string;
isMarked?: boolean;
}
@Component
export struct GestureListItem {
@Prop itemData: ListItemData;
onDelete?: (item: ListItemData) => void;
onMark?: (item: ListItemData) => void;
onAction?: (item: ListItemData, action: string) => void;
@State private showDeleteConfirm: boolean = false;
@State private showMarkFeedback: boolean = false;
@State private isPressed: boolean = false;
@State private swipeOffset: number = 0;
build() {
Column() {
SwipeGestureRecognizer({
config: {
minDistance: 40,
slowSpeedThreshold: 0.3,
fastSpeedThreshold: 0.8,
maxDuration: 800,
diagonalTolerance: 0.4
},
callbacks: {
onQuickSwipeRight: (info) => {
this.handleQuickSwipeRight(info);
},
onSlowSwipeRight: (info) => {
this.handleSlowSwipeRight(info);
},
onQuickSwipeLeft: (info) => {
this.handleQuickSwipeLeft(info);
},
onSlowSwipeLeft: (info) => {
this.handleSlowSwipeLeft(info);
}
}
}) {
Row() {
// 左侧标记指示器
if (this.itemData.isMarked) {
Column() {
Text('⭐')
.fontSize(16)
}
.width(40)
.justifyContent(FlexAlign.Center)
}
// 主要内容
Column() {
Text(this.itemData.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
if (this.itemData.subtitle) {
Text(this.itemData.subtitle)
.fontSize(12)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.margin({ top: 4 })
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 右侧箭头
Column() {
Text('>')
.fontSize(16)
.fontColor('#CCCCCC')
}
.width(30)
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height(70)
.padding({ left: 15, right: 15 })
.backgroundColor(this.isPressed ? '#F0F0F0' : '#FFFFFF')
.borderRadius(8)
.shadow({
radius: 2,
color: '#00000010',
offsetX: 0,
offsetY: 1
})
.onClick(() => {
this.onAction?.(this.itemData, 'tap');
})
.onTouch((event) => {
if (event.type === TouchType.Down) {
this.isPressed = true;
} else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
this.isPressed = false;
}
})
}
}
.width('100%')
.margin({ bottom: 8 })
}
/**
* 处理快速右滑 - 删除操作
*/
private handleQuickSwipeRight(info: SwipeInfo) {
console.log('快速右滑删除:', this.itemData.title);
this.showDeleteConfirm = true;
}
/**
* 处理慢速右滑 - 标记操作
*/
private handleSlowSwipeRight(info: SwipeInfo) {
console.log('慢速右滑标记:', this.itemData.title);
this.showMarkFeedback = true;
// 延迟后触发标记回调
setTimeout(() => {
this.showMarkFeedback = false;
this.onMark?.(this.itemData);
}, 500);
}
/**
* 处理快速左滑
*/
private handleQuickSwipeLeft(info: SwipeInfo) {
console.log('快速左滑:', this.itemData.title);
this.onAction?.(this.itemData, 'quick_swipe_left');
}
/**
* 处理慢速左滑
*/
private handleSlowSwipeLeft(info: SwipeInfo) {
console.log('慢速左滑:', this.itemData.title);
this.onAction?.(this.itemData, 'slow_swipe_left');
}
}
手势列表项演示页面demo
import { StandardTitleBar } from '../components/StandardTitleBar';
import { GestureListItem } from '../components/GestureListItem';
import showToast from '../utils/ToastUtils';
/**
* @author J.query
* @date 2025/12/26
* @email j-query@foxmail.com
* Description: 手势列表项演示页面
*/
interface DemoItem {
id: string;
title: string;
subtitle?: string;
isMarked?: boolean;
}
@Entry
@Component
struct GestureListDemo {
@State private items: DemoItem[] = [
{ id: '1', title: '视频文件.mp4', subtitle: '2025/12/26 10:30', isMarked: false },
{ id: '2', title: '会议录像.mp4', subtitle: '2025/12/25 14:20', isMarked: true },
{ id: '3', title: '教学视频.mp4', subtitle: '2025/12/24 09:15', isMarked: false },
{ id: '4', title: '产品演示.mp4', subtitle: '2025/12/23 16:45', isMarked: false },
{ id: '5', title: '用户指南.mp4', subtitle: '2025/12/22 11:30', isMarked: true },
{ id: '6', title: '培训课程.mp4', subtitle: '2025/12/21 13:20', isMarked: false },
];
@State private actionHistory: string[] = [];
aboutToAppear() {
console.log('手势列表演示页面初始化');
}
/**
* 处理删除操作
*/
private handleDelete(item: DemoItem) {
this.items = this.items.filter(i => i.id !== item.id);
this.addToHistory(`删除: ${item.title}`);
showToast(
`已删除 "${item.title}"`);
}
/**
* 处理标记操作
*/
private handleMark(item: DemoItem) {
const itemIndex = this.items.findIndex(i => i.id === item.id);
if (itemIndex !== -1) {
this.items[itemIndex].isMarked = !this.items[itemIndex].isMarked;
const action = this.items[itemIndex].isMarked ? '标记' : '取消标记';
this.addToHistory(`${action}: ${item.title}`);
}
}
/**
* 处理其他操作
*/
private handleAction(item: DemoItem, action: string) {
this.addToHistory(`${action}: ${item.title}`);
if (action === 'tap') {
console.log(`打开 "${item.title}"`);
}
}
/**
* 添加到操作历史
*/
private addToHistory(action: string) {
const timestamp = new Date().toLocaleTimeString();
this.actionHistory.unshift(`${timestamp} - ${action}`);
// 只保留最近10条记录
if (this.actionHistory.length > 10) {
this.actionHistory.pop();
}
}
build() {
Column() {
// 标题栏
StandardTitleBar({
title: '手势列表演示',
showBack: true
})
// 操作提示
Column() {
Text('🎯 手势操作说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
Row() {
Column() {
Text('➡️ 快速右滑')
.fontSize(12)
.fontColor('#FF6B6B')
Text('删除')
.fontSize(10)
.fontColor('#999999')
}
.width('30%')
Column() {
Text('➡️ 慢速右滑')
.fontSize(12)
.fontColor('#4ECDC4')
Text('标记')
.fontSize(10)
.fontColor('#999999')
}
.width('30%')
Column() {
Text('👆 点击')
.fontSize(12)
.fontColor('#45B7D1')
Text('打开')
.fontSize(10)
.fontColor('#999999')
}
.width('30%')
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.padding(15)
.backgroundColor('#F0F8FF')
.borderRadius(8)
.margin({ left: 20, top: 10, bottom: 10 })
// 列表内容
Column() {
if (this.items.length > 0) {
List({ space: 0 }) {
ForEach(this.items, (item: DemoItem) => {
ListItem() {
GestureListItem({
itemData: item,
onDelete: this.handleDelete.bind(this),
onMark: this.handleMark.bind(this),
onAction: this.handleAction.bind(this)
})
}
})
}
.width('100%')
.layoutWeight(1)
.divider({
strokeWidth: 0.5,
color: '#F0F0F0',
startMargin: 60,
endMargin: 15
})
} else {
Column() {
Text('📭')
.fontSize(48)
.fontColor('#CCCCCC')
.margin({ bottom: 10 })
Text('暂无数据')
.fontSize(16)
.fontColor('#999999')
Text('所有项目都已被删除')
.fontSize(12)
.fontColor('#CCCCCC')
.margin({ top: 5 })
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.padding({ bottom: 20 })
.layoutWeight(1)
// 操作历史
Column() {
Text('📋 操作历史')
.fontSize(12)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
.margin({ bottom: 8 })
if (this.actionHistory.length > 0) {
List({ space: 2 }) {
ForEach(this.actionHistory.slice(0, 3), (history: string) => {
ListItem() {
Text(history)
.fontSize(10)
.fontColor('#666666')
.width('100%')
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
.backgroundColor('#F8F8F8')
.borderRadius(4)
}
})
}
.width('100%')
} else {
Text('暂无操作记录')
.fontSize(10)
.fontColor('#CCCCCC')
.textAlign(TextAlign.Center)
.width('100%')
.padding(10)
}
}
.width('100%')
.padding({ left: 20, bottom: 10 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
🎯 效果

更多关于HarmonyOS鸿蒙Next中如何实现应用内手势方向+速度组合识别?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS Next中,可通过GestureGroup组合识别手势方向与速度。使用PanGesture监听滑动手势,其offset属性可判断方向(如x>0为右滑)。速度识别需结合PanGesture事件中的velocity属性(单位:像素/秒),该值反映实时速度。通过GestureGroup的parallel或sequential模式,将方向判断逻辑与速度阈值(如velocity > 1000)组合,实现特定方向与速度的复合手势识别。
在HarmonyOS Next中,可以通过Gesture和PanGesture组合监听器来实现应用内手势方向与速度的组合识别,核心在于分析PanGesture事件中的偏移量和速度向量。
以下是关键实现步骤和代码示例:
-
使用
PanGesture监听器: 这是识别滑动(平移)手势的基础。它提供了丰富的触摸事件数据。// 在自定义组件或页面的build方法中为组件添加手势 @Component struct GestureDemo { @State private offsetX: number = 0; build() { Column() { // 一个用于接收手势的容器 Stack() .width('100%') .height(200) .backgroundColor(Color.Grey) .translate({ x: this.offsetX }) // 绑定PanGesture手势识别 .gesture( PanGesture({ distance: 5 }) // distance: 5 表示最小识别距离为5vp,可过滤微小误触 .onActionStart((event: GestureEvent) => { // 手势开始,可在此初始化状态 console.info(`Gesture start at (${event.offsetX}, ${event.offsetY})`); }) .onActionUpdate((event: GestureEvent) => { // 手势移动过程,实时更新UI(例如跟随手指移动) this.offsetX = event.offsetX; }) .onActionEnd((event: GestureEvent) => { // 手势结束,这是进行方向与速度判断的关键时机 this.handleGestureEnd(event); }) .onActionCancel(() => { // 手势被取消(如来电中断),进行复位等操作 this.offsetX = 0; }) ) } } -
在手势结束时(
onActionEnd)进行判断:GestureEvent对象(在onActionEnd回调中)包含了判断所需的核心信息:- 方向判断:通过
event.offsetX和event.offsetY(从手势起点到终点的总偏移量)判断主要滑动方向。 - 速度判断:通过
event.velocity(一个Velocity对象,包含x和y分量)获取松手瞬间的滑动速度,单位是vp/秒。
private handleGestureEnd(event: GestureEvent) { // 1. 判断主要滑动方向(此处以水平滑动为例,可扩展为八方向) const isHorizontalSwipe = Math.abs(event.offsetX) > Math.abs(event.offsetY); if (isHorizontalSwipe) { if (event.offsetX > 0) { // 向右滑动 this.evaluateRightSwipe(event.velocity.x); } else { // 向左滑动 this.evaluateLeftSwipe(event.velocity.x); } } else { // 处理垂直滑动... } // 手势结束后,复位UI this.offsetX = 0; } private evaluateRightSwipe(velocityX: number) { // 2. 根据速度阈值区分操作 const FAST_SPEED_THRESHOLD = 800; // 快速阈值,单位vp/s,需根据实际体验调整 const SLOW_SPEED_THRESHOLD = 200; // 慢速阈值 const absVelocity = Math.abs(velocityX); if (absVelocity >= FAST_SPEED_THRESHOLD) { console.info('快速右滑:执行删除操作'); // 触发删除业务逻辑 } else if (absVelocity <= SLOW_SPEED_THRESHOLD) { console.info('慢速右滑:执行标记操作'); // 触发标记业务逻辑 } else { console.info('中速右滑:可执行默认操作或忽略'); } } - 方向判断:通过
关键点与优化建议:
- 阈值调优:速度阈值(
FAST_SPEED_THRESHOLD)和慢速阈值(SLOW_SPEED_THRESHOLD)需要根据具体交互场景和设备进行实测调整,以符合用户直觉。 - 方向容差:在判断主方向时,可以加入一个容差范围,例如只有当水平位移与垂直位移的比值大于2:1时才判定为水平滑动,避免斜向滑动的误判。
- 组合手势:如需更复杂识别(如长按后滑动),可将
PanGesture与LongPressGesture等通过GestureGroup进行组合(使用GestureMode.Exclusive等模式)。 - 性能:
onActionUpdate中避免频繁进行复杂计算或状态更新,以免影响跟手性能。主要判断逻辑建议放在onActionEnd中。
此方案直接利用HarmonyOS Next手势系统提供的数据,无需手动计算时间差和位移,是实现方向+速度组合识别的标准且高效方式。

