HarmonyOS应用开发实战:猫猫大作战-onTouch 三阶段触发、TouchType 类型判定、TouchObject 坐标信息、与 onCli
前言前面我们用onClick处理点击——但onClick只是「抬起」一次触发捕获不到「按下」「移动」「长按」这些中间过程。实战中很多交互要全过程响应按下时高亮、移动时拖拽、抬起时复位——比如拖动猫猫、长按出菜单、滑动手势切换。HarmonyOS 的onTouch事件能拿到完整手势三阶段Down/Move/Up是手势交互的根基。本篇以「猫猫大作战」拖动列投放 长按菜单为预演场景把onTouch 三阶段触发、TouchType 类型判定、TouchObject 坐标信息、与 onClick 的取舍四大要点讲透。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–50 篇。本篇是阶段三第一篇进入「交互与动画」阶段。一、场景拆解手势全过程「猫猫大作战」当前用onClick做列投放第 37 篇——点击某列投放猫。但玩家想要更自然的交互按下某列时高亮预览提示「要投这里」按住拖动到别列改主意改列抬起时投放确认列投放长按 800ms出菜单选猫等级/清空该列onClick只能拿到「抬起」一次做不了这些。要onTouch// 预演列点击层用 onTouch 替 onClick ForEach(this.cols, (col: number) { Column() .width(GameConfig.CELL_SIZE) .height(GameConfig.BOARD_HEIGHT * GameConfig.CELL_SIZE) .backgroundColor(this.highlightCol col ? rgba(46, 204, 113, 0.3) : rgba(0,0,0,0)) .onTouch((event: TouchEventObject) { switch (event.type) { case TouchType.Down: // 按下高亮 this.highlightCol col; this.startLongPressTimer(col); break; case TouchType.Move: // 移动暂不处理列投放不拖动 break; case TouchType.Up: // 抬起投放 清高亮 this.clearLongPressTimer(); if (this.highlightCol col) { this.handleColumnClick(col); } this.highlightCol -1; break; } }) }, (col: number) touch_${col})核心问题onTouch的三阶段Down/Move/Up分别在何时触发TouchType怎么判定当前阶段TouchObject的x/y是相对谁坐标onTouch和onClick怎么选二、onTouch 三阶段触发2.1 触发时机手指按下down→ onTouch(TouchType.Down) ↓ 手指移动move→ onTouch(TouchType.Move) 每移动一点触发一次高频 ↓ 手指抬起up→ onTouch(TouchType.Up) ↓ 之后如果算一次完整点击→ onClick阶段TouchType触发时机频率按下Down手指接触屏幕1 次移动Move手指在屏幕上移动高频每像素级抬起Up手指离开屏幕1 次关键经验onTouch 三阶段覆盖完整手势——Down 开始、Move 持续、Up 结束onClick 只在 Up 后「算一次点击」时触发丢掉了 Down/Move。2.2 事件回调签名.onTouch((event: TouchEventObject) { // event.type当前阶段Down/Move/Up // event.touches所有触摸点数组多指时多个 // event.changedTouches本阶段变化的触摸点 // event.time戳时间戳 // event.source事件源TouchScreen 等 })2.3 单指 vs 多指// 单指touches 和 changedTouches 都只有 1 个元素 const touch event.touches[0]; // 取第 0 指 // 多指如双指缩放touches 有多个 for (const touch of event.touches) { /* 处理每指 */ }实战经验消除类游戏只处理单指——取event.touches[0]即可图像缩放/地图才处理多指。三、TouchType 类型判定3.1 TouchType 枚举enum TouchType { Down 0, // 按下 Up 1, // 抬起 Move 2 // 移动 }判定当前阶段.onTouch((event: TouchEventObject) { switch (event.type) { case TouchType.Down: console.info(按下); break; case TouchType.Move: console.info(移动); break; case TouchType.Up: console.info(抬起); break; } })3.2 三阶段的典型副作用阶段典型副作用场景Down高亮、启动长按计时器、记录按下坐标按钮反馈、长按菜单、拖拽起点Move更新拖拽位置、判定滑动方向拖拽、滑动切换、手势识别Up复位高亮、清长按计时器、确认操作按钮复位、投放确认、滑动结束3.3 长按菜单的实现private longPressTimer: number -1; startLongPressTimer(col: number): void { this.longPressTimer setTimeout(() { // 800ms 后出菜单 this.showColumnMenu(col); }, 800); } clearLongPressTimer(): void { if (this.longPressTimer ! -1) { clearTimeout(this.longPressTimer); this.longPressTimer -1; } } showColumnMenu(col: number): void { // 弹出菜单第 54 篇会专讲 bindContextMenu console.info(长按列 ${col}出菜单); }流程Down →startLongPressTimer启动 800ms 计时。如果 800ms 内 Up提前抬起→clearLongPressTimer清计时算普通点击投放。如果 800ms 内没 Up长按住→ 计时到期触发showColumnMenu出菜单。关键经验长按 Down 启动计时 Up 清计时——Up 先于计时到期算点击计时先于 Up 算长按。本系列第 54 篇会专讲 bindContextMenu。四、TouchObject 坐标信息4.1 TouchObject 字段interface TouchObject { id: number; // 触摸点 id多指区分 x: number; // X 坐标 y: number; // Y 坐标 offsetX: number; // X 偏移相对上次 offsetY: number; // Y 偏移 offsetXFromStart: number; // 相对 Down 的 X 偏移 offsetYFromStart: number; // 相对 Down 的 Y 偏移 pressedTime: number; // 按下时间戳 // 等更多 }4.2 x/y 坐标系.onTouch((event: TouchEventObject) { const touch event.touches[0]; console.info(x: ${touch.x}, y: ${touch.y}); })坐标系x/y是相对触发 onTouch 的组件左上角的局部坐标。坐标含义(0, 0)触组件左上角(touch.x, touch.y)相对左上角的偏移超出组件范围也有效手指移出组件但还按着关键经验onTouch 坐标是相对组件局部的——要全局坐标用event.touches[0].xToScreen如果有或自己累加。4.3 offsetXFromStart 判定滑动.onTouch((event: TouchEventObject) { const touch event.touches[0]; switch (event.type) { case TouchType.Up: const dx touch.offsetXFromStart; const dy touch.offsetYFromStart; if (Math.abs(dx) 50 Math.abs(dy) 30) { // X 方向偏移 50 且 Y 偏移小判定左右滑动 if (dx 0) { console.info(右滑); } else { console.info(左滑); } } break; } })关键经验offsetXFromStart/offsetYFromStart是相对 Down 起点的总偏移——用来判定滑动方向最方便。4.4 pressedTime 判定长按.onTouch((event: TouchEventObject) { const touch event.touches[0]; switch (event.type) { case TouchType.Up: const duration Date.now() - touch.pressedTime; if (duration 800) { console.info(长按不靠 setTimeout 也能判定); } break; } })实战经验长按判定两种方式——Down 启动 setTimeout实时出菜单或 Up 时算Date.now() - pressedTimeUp 时判定。前者用户体验更即时800ms 到就出菜单后者要等 Up。五、onTouch vs onClick 取舍5.1 onClick 的局限.onClick(() { // 只在「完整点击」down up 且没移动太多后触发 // 拿不到 down 时机、拿不到 move 坐标、拿不到长按时长 })5.2 onTouch 的全面.onTouch((event: TouchEventObject) { // 三阶段都能响应能拿坐标、时长、偏移 })5.3 取舍决策要响应「按下」「移动」「长按」中间过程 ├─ 是 → onTouch └─ 否只要「抬起点击」一次触发 └─ onClick更简洁场景推荐普通按钮点击onClick列投放抬起确认onClick第 37 篇用的拖拽按下 移动 抬起onTouch长按菜单onTouch setTimeout滑动手势onTouch offsetXFromStart双指缩放onTouch 多指关键经验简单点击用 onClick全过程手势用 onTouch——onClick 是 onTouch 的「Up 简化版」。5.4 同时用会怎样Column() .onClick(() { console.info(click); }) .onTouch((event) { if (event.type TouchType.Up) { console.info(touch up); } }) // 抬起时先触发 onTouch(Up)再触发 onClick // 两者不冲突但通常没必要同时用实战经验通常只用一个——onClick 或 onTouch同时用要小心重复触发。六、完整预演代码拖动列投放 长按菜单// 预演Index 改造 onTouch 列投放不替换第 37 篇的 onClick作为预演 Entry Component struct Index { State gameState: GameState GameState.IDLE; State cats: Cat[] []; State highlightCol: number -1; // ← onTouch 高亮列 /* ... 其他 state */ private gameEngine: GameEngine new GameEngine(); private longPressTimer: number -1; private readonly cols: number[] [0, 1, 2, 3, 4]; /* startGame / pauseGame / resumeGame / endGame / clearTimers / formatTime / aboutToDisappear 等略 */ handleColumnClick(column: number) { if (this.gameState ! GameState.PLAYING) return; if (this.gameEngine.dropCat(column)) { this.cats this.gameEngine.getAllCats(); this.nextCatLevel this.gameEngine.getNextCatLevel(); } } // onTouch 用启动长按计时 startLongPressTimer(col: number): void { if (this.longPressTimer ! -1) clearTimeout(this.longPressTimer); this.longPressTimer setTimeout(() { this.showColumnMenu(col); }, 800); } clearLongPressTimer(): void { if (this.longPressTimer ! -1) { clearTimeout(this.longPressTimer); this.longPressTimer -1; } } showColumnMenu(col: number): void { // 弹出列菜单第 54 篇会专讲 bindContextMenu console.info(长按列 ${col}出菜单); } Builder GameView() { Column() { this.GameHUD() Column() { Row() { /* 预告区 */ } Stack() { /* 棋盘背景 */ ForEach(this.cats, (cat: Cat) { Column() { /* ... position ... */ } .position({ x: /* ... */, y: /* ... */ }) .animation({ duration: 100 }) }, (cat: Cat) cat.id) // 列点击层onTouch 替 onClick本篇预演 Row() { ForEach(this.cols, (col: number) { Column() .width(GameConfig.CELL_SIZE) .height(GameConfig.BOARD_HEIGHT * GameConfig.CELL_SIZE) // 高亮按下时变色 .backgroundColor(this.highlightCol col ? rgba(46, 204, 113, 0.3) : rgba(0,0,0,0)) .onTouch((event: TouchEventObject) { // ← onTouch switch (event.type) { case TouchType.Down: // 按下 this.highlightCol col; // 高亮 this.startLongPressTimer(col); // 启长按计时 break; case TouchType.Move: // 移动 // 列投放不拖动暂不处理 break; case TouchType.Up: // 抬起 this.clearLongPressTimer(); // 清长按计时 if (this.longPressTimer -1) { // 计时已清没出菜单算普通点击投放 // ⚠️ 注意如果长按菜单已出这里不再投放 } else { // 简化只要抬起就投放实际要区分长按是否已触发菜单 } if (this.highlightCol col) { this.handleColumnClick(col); } this.highlightCol -1; // 清高亮 break; } }) }, (col: number) touch_${col}) } } .width(GameConfig.BOARD_WIDTH * GameConfig.CELL_SIZE) .height(GameConfig.BOARD_HEIGHT * GameConfig.CELL_SIZE) .borderRadius(12).clip(true).backgroundColor(#D6EEF5) }.alignItems(HorizontalAlign.Center) Spacer() Row() { /* 底部控制栏 */ } .width(100%).padding({ left: 24, right: 24, bottom: 24, top: 12 }) } .width(100%).height(100%) .linearGradient({ direction: GradientDirection.Bottom, colors: [[#E8F4F8, 0.0], [#D6EEF5, 0.5], [#C9E8F2, 1.0]] }) .alignItems(HorizontalAlign.Center) } /* GameHUD / MainMenuView / PauseOverlay / GameOverOverlay / StatItem 等略 */ }七、踩坑提示7.1 onTouch 用普通函数丢 this// ❌ 错误普通函数 this 不指向组件 .onTouch(function (event) { this.highlightCol col; }) // ✅ 正确箭头函数保留 this第 38 篇讲过 .onTouch((event: TouchEventObject) { this.highlightCol col; })7.2 忘清长按计时器// ❌ 错误Down 启动计时但 Up 忘清导致抬起后还出菜单 case TouchType.Down: this.startLongPressTimer(col); break; // 忘了 case TouchType.Up: this.clearLongPressTimer(); // ✅ 正确Up 清计时 case TouchType.Up: this.clearLongPressTimer(); // ... break;7.3 Move 阶段做重计算// ❌ 错误Move 高频触发做重计算会卡 case TouchType.Move: this.heavyCalc(); // 每像素级都调卡死 // ✅ 正确Move 轻量重计算延迟到 Up case TouchType.Move: this.lastMoveX event.touches[0].x; // 只记录 break; case TouchType.Up: this.heavyCalc(this.lastMoveX); // Up 时才算 break;7.4 多指取错触摸点// ❌ 错误多指时取 touches[0] 可能不是当前变化指 const touch event.touches[0]; // ✅ 正确用 changedTouches 取本阶段变化的指 const touch event.changedTouches[0];7.5 坐标系误解// ❌ 错误以为 touch.x 是全局坐标 const globalX touch.x; // 实际是相对组件左上角 // ✅ 正确知道是局部坐标 const localX touch.x; // 相对组件左上角 // 要全局坐标自己累加或用 screen 系 API ## 八、调试技巧 1. **console.info 打 event.type**追三阶段触发时机。 2. **console.info 打 touch.x/y**追坐标验证是否相对组件局部。 3. **长按菜单不出排查**检查 Down 是否启动计时检查 Up 是否过早清了计时计时还没到期就清。 4. **长按菜单抬起了还出排查**检查 Up 是否清计时检查 setTimeout 是否清成功。 ## 九、性能与最佳实践 1. **简单点击用 onClick全过程手势用 onTouch**——onClick 是 onTouch 的 Up 简化版。 2. **onTouch 回调用箭头函数保留 this**——普通函数 this 丢失第 38 篇。 3. **Down 启动计时Up 清计时**——长按菜单的标准模式Up 必清避免泄漏。 4. **Move 阶段别做重计算**——高频触发只记录坐标重计算延迟到 Up。 5. **单指取 touches[0]多指用 changedTouches**——区分当前变化的指。 6. **坐标是相对组件局部的**——要全局自己累加或用 screen API。 ## 十、阶段三进度开篇51–55 本篇是阶段三「交互与动画」第 1 篇开篇覆盖 | 篇 | 主题 | 核心要点 | |----|------|----------| | 51本篇 | onTouch | 手势三阶段 Down/Move/UpTouchType 判定坐标信息 | | 52 | onHover | 悬停事件鼠标/遥控器悬停反馈 | | 53 | onKeyEvent | 键盘/遥控器按键事件KeyCode 判定 | | 54 | bindContextMenu | 右键/长按上下文菜单 | | 55 | animateTo | 显式动画触发动画闭包 | 接下来阶段三还会覆盖animation 属性动画、Hero 共享元素、transition 转场、spring 物理、Hero Style Player 等。 ## 总结 本篇我们从 onTouch 手势切入掌握了**三阶段触发Down/Move/Up**、**TouchType 判定**、**TouchObject 坐标局部 offsetFromStart**、**与 onClick 的取舍**四大要点并给出了拖动列投放 长按菜单的完整预演代码。核心要点**onClick 只触发抬起onTouch 覆盖全过程Down 启计时 Up 清计时做长按Move 轻量重计算延到 Up坐标相对组件局部**。 下一篇我们将拆解 onHover——悬停事件。 如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力 --- **相关资源** - 「猫猫大作战」项目源码本仓库 entry/src/main/ets/pages/Index.ets - [ArkUI onTouch 触摸事件官方指南](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-events-touch) - [TouchType 枚举官方文档](https://developer.huawei.com/consumer/cn/references/ts-touch-type) - [TouchEventObject 接口官方文档](https://developer.huawei.com/consumer/cn/references/ts-touch-event) - [HarmonyOS 手势处理最佳实践](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/bpta-gesture-processing) - [开源鸿蒙跨平台社区](https://openharmonycrossplatform.csdn.net) - [HarmonyOS 开发者官方文档首页](https://developer.huawei.com/consumer/cn/doc/) - 系列索引本仓库 articles/INDEX.md

相关新闻

HarmonyOS应用开发实战:猫猫大作战-浅观察陷阱与嵌套对象更新

HarmonyOS应用开发实战:猫猫大作战-浅观察陷阱与嵌套对象更新

前言 前面我们多次遇到「浅观察」——State combo: ComboInfo,改 this.combo.count 5 不触发重渲染,必须 this.combo {...} 整体赋值。这是 V1 状态管理最经典的坑——嵌套对象/数组内部属性变化监听不到。本篇专攻这个陷阱,把浅观察的本质…

2026/7/28 3:16:52 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-@Link 声明与 $val 传参、双向同步机制、@Link vs @Prop vs @Event 取舍、

HarmonyOS应用开发实战:猫猫大作战-@Link 声明与 $val 传参、双向同步机制、@Link vs @Prop vs @Event 取舍、

前言 上一篇我们用 Prop 把得分从父传给子 HUD——单向只读,子改不了父。但有些场景子组件要直接改父的数据:表单子组件改父的 username、滑块子组件改父的音量、暂停弹窗里的「继续/重开」按钮改父的 gameState。这时 Prop 不够用,要用 Lin…

2026/7/28 3:16:52 阅读更多 →
影刀RPA实战教程:7个真实案例带你从入门到独立开发

影刀RPA实战教程:7个真实案例带你从入门到独立开发

影刀RPA实战教程:7个真实案例带你从入门到独立开发 教程看了不少,一到自己写就无从下手。这很正常——RPA是做的技能,不是看的技能。这篇直接用7个真实案例串起来,从简单到复杂,每个案例都能直接拿去用或者改成你自己…

2026/7/28 3:16:51 阅读更多 →

最新新闻

nRPC快速上手:10分钟搭建你的第一个NATS RPC服务

nRPC快速上手:10分钟搭建你的第一个NATS RPC服务

nRPC快速上手:10分钟搭建你的第一个NATS RPC服务 【免费下载链接】nrpc nRPC is like gRPC, but over NATS 项目地址: https://gitcode.com/gh_mirrors/nr/nrpc nRPC是一个基于NATS的RPC框架,类似于gRPC但使用NATS作为传输层。本文将带你快速上手…

2026/7/28 3:27:55 阅读更多 →
革命性百度贴吧个性化定制模块:TiebaTS重塑你的浏览体验

革命性百度贴吧个性化定制模块:TiebaTS重塑你的浏览体验

革命性百度贴吧个性化定制模块:TiebaTS重塑你的浏览体验 【免费下载链接】TiebaTS 提供修改百度贴吧底栏等个性化功能。An Xposed module for Baidu Tieba with personalized functions. 项目地址: https://gitcode.com/gh_mirrors/ti/TiebaTS TiebaTS是一款…

2026/7/28 3:27:55 阅读更多 →
RS485转UART通信实战:从硬件连接到Modbus协议解析

RS485转UART通信实战:从硬件连接到Modbus协议解析

1. 项目概述:从串口协议到气象数据采集最近在做一个环境监测的小项目,需要同时采集风速和风向数据。手头正好有一个RS485接口的风速风向仪,但主控板用的是Arduino Uno和micro:bit。这俩板子自带的都是UART(也就是常说的串口&#…

2026/7/28 3:27:55 阅读更多 →
如何高效管理坎巴拉太空计划模组:CKAN终极指南

如何高效管理坎巴拉太空计划模组:CKAN终极指南

如何高效管理坎巴拉太空计划模组:CKAN终极指南 【免费下载链接】CKAN The Comprehensive Kerbal Archive Network 项目地址: https://gitcode.com/gh_mirrors/cka/CKAN 还在为《坎巴拉太空计划》模组安装的繁琐流程而头疼吗?CKAN作为KSP社区最受欢…

2026/7/28 3:27:55 阅读更多 →
数据立方体优化:OLAP性能提升策略与实践

数据立方体优化:OLAP性能提升策略与实践

1. 数据立方体在OLAP中的核心价值 在商业智能和数据分析领域,数据立方体(Data Cube)是OLAP(联机分析处理)系统的核心数据结构。它通过预计算和多维聚合的方式,将海量数据转化为可快速分析的形态。想象一下&…

2026/7/28 3:27:55 阅读更多 →
环结构电磁场多极子分解原理与COMSOL实现

环结构电磁场多极子分解原理与COMSOL实现

1. 项目概述:环结构电磁场的多极子分解原理在电磁场仿真领域,环结构作为一种基础却重要的几何构型,广泛存在于电感器、天线设计和磁共振系统中。传统仿真往往止步于场分布的可视化,而多极子分解技术为我们打开了理解电磁场本质的新…

2026/7/28 3:26:54 阅读更多 →

日新闻

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:43 阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:43 阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:43 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/27 4:33:59 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/27 6:31:56 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/27 4:01:12 阅读更多 →

月新闻