HarmonyOS应用开发实战:猫猫大作战-onHover 触发时机、HoverType 类型判定、与 onTouch 的差异、TV/PC 场景应用四

📅 2026/7/28 22:31:48
HarmonyOS应用开发实战:猫猫大作战-onHover 触发时机、HoverType 类型判定、与 onTouch 的差异、TV/PC 场景应用四
前言上一篇我们用onTouch处理了手势全过程——按下/移动/抬起。但有种交互不靠「接触」而靠「接近」——鼠标悬停、遥控器焦点停留、电视遥控方向选中。在 TV/Pad 遥控器和桌面鼠标场景下用户不点击就「悬停」在某控件上要给反馈高亮/提示/展开子菜单。HarmonyOS 的onHover事件捕获「进入/离开」两种状态是 TV/PC 应用的必备。本篇以「猫猫大作战」底部按钮悬停高亮为预演场景把onHover 触发时机、HoverType 类型判定、与 onTouch 的差异、TV/PC 场景应用四大要点讲透。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–51 篇。本篇是阶段三第二篇。一、场景拆解底部按钮悬停反馈「猫猫大作战」底部控制栏第 11、32 篇有「暂停/重新开始」两个按钮。手机端用户点击就触发——但 TV 端用户用遥控器方向键选中时焦点停留但未点击要给「这个按钮被选中了」的视觉反馈// 预演底部按钮加 onHover 悬停高亮 Row() { Button(暂停) .backgroundColor(this.hoverBtn pause ? #3498DB : #95A5A6) .onHover((isHover: boolean) { if (isHover) { this.hoverBtn pause; // 焦点进入高亮 } else { this.hoverBtn ; // 焦点离开复位 } }) .onClick(() { this.pauseGame(); }) Spacer() Button(重新开始) .backgroundColor(this.hoverBtn restart ? #E67E22 : #95A5A6) .onHover((isHover: boolean) { if (isHover) { this.hoverBtn restart; } else { this.hoverBtn ; } }) .onClick(() { this.clearTimers(); this.startGame(); }) }核心问题onHover何时触发和onTouch的「按下」有什么不同isHover: boolean怎么判定进入还是离开TV 端遥控器、PC 端鼠标、手机端手指三种输入分别怎么触发手机端需要 onHover 吗二、onHover 触发时机2.1 两种触发指针进入组件边界 → onHover(true) ↓ 指针在组件内移动不重复触发与 onTouch 不同 ↓ 指针离开组件边界 → onHover(false)事件触发时机参数onHover(true)指针进入组件边界isHover trueonHover(false)指针离开组件边界isHover false关键经验onHover 只触发「进入/离开」两次——指针在组件内移动不重复触发不像 onTouch 的 Move 高频。2.2 事件回调签名.onHover((isHover: boolean) { // isHover: true 进入false 离开 })2.3 与 onTouch 的关键差异维度onTouchonHover触发条件必须接触手指/鼠标按下不接触鼠标移动/遥控焦点阶段Down/Move/Up 三阶段进入/离开两阶段移动时高频 Move 触发不触发只边界进出手机端手指触摸触发❌ 手机无悬停概念TV 端❌ 遥控器无接触✅ 焦点停留触发PC 端鼠标按下触发✅ 鼠标移过触发关键经验onTouch 要「接触」onHover 要「接近」——手机端用 onTouchTV/PC 端补 onHover。三、HoverType 类型判定进阶3.1 基本用法isHover 布尔最简形式就是isHover: boolean.onHover((isHover: boolean) { if (isHover) { this.hoverBtn pause; } else { this.hoverBtn ; } })3.2 进阶HoverEventObject 拿坐标某些版本 ArkUI 的 onHover 也能传HoverEventObject拿坐标.onHover((event: HoverEventObject) { // event.type: HoverTypeEnter/Exit/Move 在某些版本 // event.x, event.y: 指针相对组件的局部坐标 // event.isHover: boolean })判定进入/离开.onHover((event: HoverEventObject) { switch (event.type) { case HoverType.Enter: // 进入 this.hoverBtn pause; break; case HoverType.Exit: // 离开 this.hoverBtn ; break; } })提示不同 ArkUI 版本的 onHover 签名可能不同——有的传 boolean有的传 HoverEventObject。以实际 SDK 为准最简 boolean 形式兼容性最好。3.3 坐标信息.onHover((event: HoverEventObject) { console.info(悬停在 (${event.x}, ${event.y})); })坐标系和 onTouch 一样x/y是相对组件左上角的局部坐标。实战经验消除类游戏通常只用 boolean 形式——不关心具体悬停位置只关心「是否悬停」。四、三种输入设备的触发差异4.1 鼠标PC 端鼠标移入组件边界 → onHover(true) 鼠标在组件内移动 → 不触发只边界进出 鼠标移出组件边界 → onHover(false) 鼠标按下 → onTouch(Down) 鼠标抬起 → onTouch(Up) onClickPC 端典型交互鼠标移入高亮onHover点击触发onClick。4.2 遥控器TV 端方向键选中某控件 → 控件获得焦点 → onHover(true) 方向键移到别控件 → 原控件失焦 → onHover(false)新控件 onHover(true) 确认键按下 → onClickTV 端典型交互方向键选中高亮onHover确认键触发onClick。4.3 手指手机端手指悬停→ ❌ 手机无悬停概念 手指触摸 → onTouch(Down) 手指抬起 → onTouch(Up) onClick手机端典型交互手指触摸高亮onTouch Down抬起触发onClick。4.4 三设备对照设备悬停事件接触事件点击事件PC 鼠标✅ onHoveronTouchonClickTV 遥控✅ onHover焦点❌onClick确认键手机手指❌onTouchonClick关键经验手机端不需要 onHover——但写上无害手机不触发。跨设备应用统一加 onHover手机端自动忽略。五、实战底部按钮悬停高亮5.1 改造底部控制栏// 预演Index 底部控制栏加 onHover第 11、32 篇的底部栏改造 Entry Component struct Index { State gameState: GameState GameState.IDLE; State hoverBtn: string ; // ← onHover 状态哪按钮悬停中 /* ... 其他 state */ /* startGame / pauseGame / resumeGame / endGame / clearTimers / formatTime / aboutToDisappear 筑略 */ Builder GameView() { Column() { this.GameHUD() Column() { Row() { /* 预告区 */ } Stack() { /* 棋盘背景、猫咪渲染、列点击层 */ } .width(GameConfig.BOARD_WIDTH * GameConfig.CELL_SIZE) .height(GameConfig.BOARD_HEIGHT * GameConfig.CELL_SIZE) .borderRadius(12).clip(true).backgroundColor(#D6EEF5) }.alignItems(HorizontalAlign.Center) Spacer() // 底部控制栏onHover 悬停高亮本篇预演 Row() { Button(暂停) .width(120).height(44) .fontSize(16).fontWeight(FontWeight.Medium) .fontColor(#FFFFFF) // 悬停高亮hoverBtn pause 时变深色 .backgroundColor(this.hoverBtn pause ? #3498DB : #95A5A6) .borderRadius(22) .onHover((isHover: boolean) { // ← onHover this.hoverBtn isHover ? pause : ; }) .onClick(() { this.pauseGame(); }) Spacer() Button(重新开始) .width(120).height(44) .fontSize(16).fontWeight(FontWeight.Medium) .fontColor(#FFFFFF) .backgroundColor(this.hoverBtn restart ? #E67E22 : #95A5A6) .borderRadius(22) .onHover((isHover: boolean) { // ← onHover this.hoverBtn isHover ? restart : ; }) .onClick(() { this.clearTimers(); this.startGame(); }) } .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 等略 */ }5.2 触发流程PC 鼠标场景鼠标移入「暂停」按钮 →onHover(true)→hoverBtn pause→ 按钮变深蓝色高亮。鼠标点击 →onClick→pauseGame()。鼠标移出「暂停」按钮 →onHover(false)→hoverBtn → 按钮变回灰色。TV 遥控场景方向键选中「暂停」按钮 →onHover(true)→ 高亮。确认键按下 →onClick→pauseGame()。方向键移到「重新开始」→ 「暂停」onHover(false)复位「重新开始」onHover(true)高亮。手机手指场景onHover不触发hoverBtn始终为按钮始终灰色——但用户点击时按钮有 onClick 反馈不影响功能。六、进阶悬停展开子菜单6.1 悬停展开下拉菜单State hoverMenu: boolean false; Column() { Button(设置) .onHover((isHover: boolean) { this.hoverMenu isHover; // 悬停时展开菜单 }) if (this.hoverMenu) { Column() { Text(音效设置).onClick(() { /* ... */ }) Text(震动设置).onClick(() { /* ... */ }) Text(语言设置).onClick(() { /* ... */ }) } .backgroundColor(#FFFFFF) .borderRadius(8) .padding(8) } }实战经验悬停展开菜单在 PC 端常见——但 TV 端遥控器方向键要能进入子菜单要配 focusable tabindex第 53 篇会专讲按键。6.2 悬停 Tooltip 提示State hoverTip: string ; Stack() { Button(暂停) .onHover((isHover: boolean) { this.hoverTip isHover ? 点击暂停游戏 : ; }) if (this.hoverTip ! ) { Text(this.hoverTip) .fontSize(12).fontColor(#FFFFFF) .backgroundColor(rgba(0,0,0,0.8)) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .borderRadius(4) .position({ x: 0, y: -30 }) // 显示在按钮上方 } }关键经验Tooltip 悬停提示是 PC 端惯例——手机端用长按出 TooltiponTouch setTimeout。七、踩坑提示7.1 onHover 用普通函数丢 this// ❌ 错误普通函数 this 不指向组件 .onHover(function (isHover) { this.hoverBtn pause; }) // ✅ 正确箭头函数保留 this第 38 篇讲过 .onHover((isHover: boolean) { this.hoverBtn isHover ? pause : ; })7.2 忘复位离开状态// ❌ 错误进入高亮离开忘复位按钮一直高亮 .onHover((isHover: boolean) { if (isHover) { this.hoverBtn pause; } // 忘了 else { this.hoverBtn ; } }) // ✅ 正确进入/离开都处理 .onHover((isHover: boolean) { this.hoverBtn isHover ? pause : ; })7.3 手机端期望 onHover 触发// ❌ 错误手机端测试发现 onHover 不触发以为 bug // 实际手机无悬停概念onHover 只在 PC/TV 触发 // ✅ 正确跨设备统一加 onHover手机端自动忽略手机端高亮用 onTouch Down Column() .onHover((isHover) { this.hoverBtn isHover ? pause : ; }) // PC/TV .onTouch((event) { // 手机 if (event.type TouchType.Down) { this.hoverBtn pause; } else if (event.type TouchType.Up) { this.hoverBtn ; } }) .onClick(() { this.pauseGame(); })7.4 期望悬停时持续触发// ❌ 错误期望指针在组件内移动时持续触发 onHover .onHover((isHover) { this.updatePosition(); // 以为会持续调实际只进出两次 }) // ✅ 正确悬停要持续响应用 onTouch如果接触或鼠标移动事件 .onMouse((event) { // 鼠标移动事件PC 专 if (event.action MouseAction.Move) { this.updatePosition(); } })八、调试技巧console.info打 isHover追触发时机验证是否只在边界进出。PC 端用 DevEco 预览器鼠标测试移入移出看 onHover 日志。TV 端用遥控器模拟器方向键选中看 onHover 日志。手机端不触发排查确认是正常行为手机无悬停不是 bug手机端高亮改用 onTouch。九、性能与最佳实践PC/TV 跨设备应用加 onHover——手机端自动忽略不冲突。onHover 只触发进入/离开——指针在组件内移动不重复触发不像 onTouch Move。进入高亮离开复位——忘复位按钮一直高亮。悬停展开菜单/Tooltip 在 PC 端常见——TV 端要配 focusable 让方向键能进入。手机端高亮用 onTouch Down——手机无悬停触摸按下时高亮。回调用箭头函数保留 this——普通函数 this 丢失。十、阶段三进度51–55本篇是阶段三「交互与动画」第 2 篇篇主题核心要点51onTouch手势三阶段 Down/Move/Up52本篇onHover悬停进入/离开TV/PC 场景53onKeyEvent键盘/遥控器按键54bindContextMenu右键/长按上下文菜单55animateTo显式动画触发总结本篇我们从 onHover 悬停切入掌握了触发时机进入/离开两次、isHover 布尔判定、与 onTouch 的差异接近 vs 接触、**三设备输入差异PC/TV 触发手机不触发**四大要点并给出了底部按钮悬停高亮的完整代码。核心要点onHover 只触发进出两次PC/TV 跨设备加手机端自动忽略改用 onTouch进入高亮离开复位。下一篇我们将拆解 onKeyEvent——键盘/遥控器按键事件。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源「猫猫大作战」项目源码本仓库entry/src/main/ets/pages/Index.etsArkUI onHover 悬停事件官方指南HarmonyOS TV/PC 跨设备适配最佳实践ArkUI 鼠标与遥控器事件官方指南开源鸿蒙跨平台社区HarmonyOS 开发者官方文档首页系列索引本仓库articles/INDEX.md