news 2026/6/25 8:42:31

HarmonyOS 事件监听为什么点击地图 Marker 没反应?事件绑定详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS 事件监听为什么点击地图 Marker 没反应?事件绑定详解

文章目录

    • 前言
    • 一、什么是事件监听?
      • 1.1 生活中的类比
      • 1.2 在代码中的形态
    • 二、项目源码中的 on/off:MapKit 事件
      • 2.1 地图组件的两个核心事件
    • 三、自实现事件发布订阅系统
      • 3.1 手写一个简单的 EventEmitter
      • 3.2 在组件间通信中使用
    • 四、HarmonyOS emitter 模块:系统级事件总线
      • 4.1 使用系统 emitter
    • 五、内存泄漏:最容易忽略的问题
      • 5.1 内存泄漏的场景
      • 5.2 正确做法:配对使用 on/off
    • 六、MapKit 事件类型速查
    • 总结

前言

在 HarmonyOS 开发中,很多系统组件(地图、传感器、网络状态)采用事件监听(on/off)模式来通知应用程序发生了某件事。本项目地图页面的核心交互——点击 Marker 显示弹窗、点击定位按钮移动地图——都通过mapController.on(...)实现。

理解事件监听模式,是掌握 HarmonyOS SDK 的关键。本篇从原理到实战,带你彻底搞懂 on/off 模式。

一、什么是事件监听?

1.1 生活中的类比

你订阅了外卖App的配送通知: on('配送到楼下', () => { 下楼取餐 }) ← 注册监听 外卖员到楼下时: emit('配送到楼下') ← 触发事件(由系统/框架完成) 你下楼取餐 ← 你的回调被执行 你不需要外卖通知了: off('配送到楼下') ← 取消监听

1.2 在代码中的形态

// 注册事件监听(订阅)emitter.on('eventName',(data)=>{// 事件发生时的处理逻辑});// 某个时刻,系统触发事件emitter.emit('eventName',{key:'value'});// 取消监听(退订),防止内存泄漏emitter.off('eventName');

二、项目源码中的 on/off:MapKit 事件

2.1 地图组件的两个核心事件

GasStationPage.etsinit()方法中:

this.callback=async(err,mapController):Promise<void>=>{if(err){return;}this.mapController=mapController;// 事件1:监听"我的位置"按钮点击this.mapController.on('myLocationButtonClick',()=>{Logger.info('testTag','Jump to my location');mapUtil.moveToMyLocation(mapController).then(()=>{this.isShow=true;// 显示弹窗});});// 事件2:监听 Marker(标记点)点击this.mapController.on('markerClick',(marker)=>{this.isShow=true;// 显示弹窗marker.setInfoWindowVisible(true);// 显示信息窗this.curMarker=marker;// 记录当前 Markerthis.imageScale=1.5;// 设置放大比例mapUtil.imageAnimation(marker,this.imageScale);// 执行放大动画// 移动地图到该加油站位置mapUtil.moveToCurrentPosition(marker.getPosition().latitude,marker.getPosition().longitude,mapController);});};

事件流程:

用户点击地图上的 Marker │ ▼ MapKit 触发 'markerClick' 事件,传入被点击的 marker 对象 │ ▼ 我们注册的回调函数执行 │ ├── isShow = true → bindSheet 弹窗出现 ├── marker 放大动画 └── 地图相机移动到该位置

三、自实现事件发布订阅系统

3.1 手写一个简单的 EventEmitter

理解 on/off 最好的方法是自己实现一个:

// 事件回调类型typeEventCallback<T=unknown>=(data:T)=>void;// 事件发布订阅类classEventEmitter{// 用 Map 存储:事件名 → 回调函数列表privatelisteners:Map<string,EventCallback[]>=newMap();// 注册事件监听on<T>(eventName:string,callback:EventCallback<T>):void{if(!this.listeners.has(eventName)){this.listeners.set(eventName,[]);}this.listeners.get(eventName)!.push(callbackasEventCallback);console.log(`[EventEmitter] 注册事件:${eventName},当前监听数:${this.listeners.get(eventName)!.length}`);}// 注册一次性监听(触发一次后自动移除)once<T>(eventName:string,callback:EventCallback<T>):void{constwrapper:EventCallback<T>=(data:T)=>{callback(data);this.off(eventName,wrapperasEventCallback);// 执行后立即移除};this.on(eventName,wrapper);}// 触发事件emit<T>(eventName:string,data?:T):void{constcallbacks=this.listeners.get(eventName);if(!callbacks||callbacks.length===0){console.warn(`[EventEmitter] 没有${eventName}的监听器`);return;}callbacks.forEach(cb=>cb(dataasunknown));console.log(`[EventEmitter] 触发事件:${eventName},通知了${callbacks.length}个监听器`);}// 取消特定监听off(eventName:string,callback?:EventCallback):void{if(!callback){// 不传 callback:移除该事件的所有监听this.listeners.delete(eventName);console.log(`[EventEmitter] 移除事件${eventName}的所有监听器`);return;}constcallbacks=this.listeners.get(eventName);if(callbacks){constfiltered=callbacks.filter(cb=>cb!==callback);this.listeners.set(eventName,filtered);}}// 清理所有监听(组件销毁时调用)removeAllListeners():void{this.listeners.clear();console.log('[EventEmitter] 已清理所有事件监听器');}// 查看事件监听数量(调试用)listenerCount(eventName:string):number{returnthis.listeners.get(eventName)?.length??0;}}// 全局事件总线(单例)exportconstglobalBus=newEventEmitter();

3.2 在组件间通信中使用

// 定义事件类型(避免拼写错误)constEVENTS={STATION_SELECTED:'station:selected',MAP_MOVED:'map:moved',LOCATION_UPDATED:'location:updated',}asconst;interfaceStationSelectedEvent{stationId:string;latitude:number;longitude:number;}// 组件A:发布事件(地图页面)@Componentstruct MapPage{onMarkerClick(stationId:string,lat:number,lng:number):void{// 发布"加油站被选中"事件globalBus.emit<StationSelectedEvent>(EVENTS.STATION_SELECTED,{stationId,latitude:lat,longitude:lng});}build(){// ...Text('点击触发事件').onClick(()=>{this.onMarkerClick('001',40.0046,116.4823);})}}// 组件B:订阅事件(列表页面)@Componentstruct StationListPage{@StateselectedId:string='';aboutToAppear():void{// 注册监听globalBus.on<StationSelectedEvent>(EVENTS.STATION_SELECTED,(event)=>{this.selectedId=event.stationId;console.log(`站点被选中:${event.stationId}`);});}aboutToDisappear():void{// ⚠️ 必须在组件销毁时取消监听,防止内存泄漏!globalBus.off(EVENTS.STATION_SELECTED);}build(){Text(`当前选中:${this.selectedId}`)}}

四、HarmonyOS emitter 模块:系统级事件总线

4.1 使用系统 emitter

HarmonyOS 提供了系统级的@kit.BasicServicesKit中的emitter模块:

import{emitter}from'@kit.BasicServicesKit';// 定义事件ID(用数字或字符串)constSTATION_CLICK_EVENT:emitter.InnerEvent={eventId:1001};// 订阅事件(在页面A)emitter.on(STATION_CLICK_EVENT,(data:emitter.EventData)=>{conststationId=data.data?.['stationId']asstring;console.log(`收到事件,站点ID:${stationId}`);});// 发布事件(在页面B)emitter.emit(STATION_CLICK_EVENT,{data:{stationId:'001',name:'望京加油站'}});// 取消订阅emitter.off(STATION_CLICK_EVENT);// 只订阅一次emitter.once(STATION_CLICK_EVENT,(data:emitter.EventData)=>{console.log('只执行一次的回调');});

五、内存泄漏:最容易忽略的问题

5.1 内存泄漏的场景

@Componentstruct LeakyComponent{aboutToAppear():void{// 注册了监听globalBus.on('someEvent',this.handleEvent);}handleEvent=()=>{console.log('事件触发');this.doSomething();// 如果组件已销毁,this 还在被引用!};// ❌ 忘记在 aboutToDisappear 中取消监听// 结果:组件销毁后,globalBus 仍然持有 this 的引用// 导致:组件无法被垃圾回收 → 内存泄漏// 更糟:后续事件触发时,已销毁的组件仍然被调用 → 可能崩溃build(){Text('组件')}}

5.2 正确做法:配对使用 on/off

@Componentstruct SafeComponent{// 保存回调引用(用于精确取消)privatehandleEvent:(data:unknown)=>void=()=>{};aboutToAppear():void{// 保存回调函数引用this.handleEvent=(data:unknown)=>{console.log('事件触发,数据:',data);};// 注册监听globalBus.on('someEvent',this.handleEvent);}aboutToDisappear():void{// ✅ 组件销毁时,精确取消该回调的监听globalBus.off('someEvent',this.handleEvent);}build(){Text('安全组件')}}

提示:on 和 off 必须成对出现。在aboutToAppear注册的监听,必须在aboutToDisappear中取消。这是 HarmonyOS 开发的黄金法则。

六、MapKit 事件类型速查

事件名触发时机回调参数
markerClick点击地图标记marker: map.Marker
myLocationButtonClick点击定位按钮
mapClick点击地图空白处latLng: mapCommon.LatLng
cameraMove地图相机移动中cameraPosition
cameraIdle地图相机停止移动cameraPosition
infoWindowClick点击信息窗marker: map.Marker

总结

事件监听(on/off)模式是 HarmonyOS 系统组件与应用代码解耦通信的核心机制。掌握三点:①on注册监听并保存回调引用,②事件触发时执行业务逻辑,③off在组件销毁时精确取消监听避免内存泄漏。理解了这个模式,你就能读懂 MapKit、传感器、网络状态等所有基于事件驱动的 HarmonyOS SDK。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 8:37:50

无人机航拍树木识别数据集训练及应用

航拍树木识别数据集 7432 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含1种分类&#xff1a;names: [‘tree’]&#xff0c;代表树木数据集来自国内外网站、视频抽帧&#xff1b; 可用于无人机树木检测等。 检测场景为道路&#xff0c;园区&#xff0…

作者头像 李华
网站建设 2026/6/11 3:46:41

leetcode 2196. 根据描述创建二叉树 中等

给你一个二维整数数组 descriptions &#xff0c;其中 descriptions[i] [parenti, childi, isLefti] 表示 parenti 是 childi 在 二叉树 中的 父节点&#xff0c;二叉树中各节点的值 互不相同 。此外&#xff1a;如果 isLefti 1 &#xff0c;那么 childi 就是 parenti 的左子…

作者头像 李华
网站建设 2026/6/8 14:53:55

3分钟学会猫抓插件:新手也能轻松下载网页视频音频的完整指南

3分钟学会猫抓插件&#xff1a;新手也能轻松下载网页视频音频的完整指南 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾遇到在线视频无法…

作者头像 李华
网站建设 2026/6/8 14:53:31

从MCAL/SDK到RTD:嵌入式驱动架构迁移实战与避坑指南

1. 项目概述与核心价值在嵌入式开发领域&#xff0c;尤其是汽车电子和工业控制这类对实时性、可靠性和安全性要求极高的场景&#xff0c;软件架构的每一次演进都牵动着无数工程师的神经。最近几年&#xff0c;一个明显的趋势是从传统的、相对独立的软件开发套件&#xff08;SDK…

作者头像 李华
网站建设 2026/6/8 14:51:07

从ResNet到GAN:手把手拆解反卷积(转置卷积)在CV模型里的核心作用

从ResNet到GAN&#xff1a;手把手拆解反卷积&#xff08;转置卷积&#xff09;在CV模型里的核心作用计算机视觉领域的技术演进如同一部精密编织的史诗&#xff0c;而反卷积技术则是其中一条贯穿始终的金线。这项最初用于网络可视化的小工具&#xff0c;如今已成为生成对抗网络、…

作者头像 李华