前言

"附近加油站"的核心功能是地图,地图来自华为 MapKit。MapKit 是华为提供的地图能力套件,类似高德地图 SDK 或 Google Maps SDK,但深度集成在 HarmonyOS 里。

这篇文章从头讲 MapKit 的接入流程,把 GasStationPage.ets 里地图相关的代码全部讲清楚。

项目预览

一、MapKit 的核心类

使用 MapKit 需要了解三个核心对象:

对象 作用
MapComponent 地图 UI 组件,嵌入到 build() 里显示地图
mapCommon.MapOptions 地图初始化配置(中心点、缩放级别、地图类型等)
map.MapComponentController 地图控制器,控制地图行为(移动、添加标记、监听事件)

三者的关系:

MapOptions(配置参数)
    ↓ 传给
MapComponent(UI显示)
    ↓ 通过 callback 返回
MapComponentController(控制器)
    ↓ 用于
  添加标记、移动镜头、监听点击...

二、声明相关变量

// GasStationPage.ets
@Component
struct GasStationPage {
  private mapOptions?: mapCommon.MapOptions;                    // 地图配置(可选,初始为空)
  private mapController?: map.MapComponentController;          // 地图控制器(回调里赋值)
  private callback?: AsyncCallback<map.MapComponentController>; // 初始化回调
}

这三个属性都是 private(私有的),只在组件内部使用。都带 ? 表示可选,初始值为 undefined,在 init() 方法里初始化。

三、初始化 mapOptions

async init(): Promise<void> {
  // 配置地图初始化选项
  this.mapOptions = {
    position: {
      target: {
        latitude: this.latitude,    // 初始中心点纬度(初始为0)
        longitude: this.longitude,  // 初始中心点经度(初始为0)
      },
      zoom: 16,                     // 初始缩放级别(1-20,16约等于街道级别)
    },
    myLocationControlsEnabled: true,  // 显示"我的位置"按钮
    mapType: mapCommon.MapType.STANDARD, // 地图类型:标准地图
  };
}

3.1 zoom 缩放级别

zoom 值 显示范围
1 全球视图
5 国家/大陆
10 城市
15 街区
16 街道(本项目使用)
20 建筑物

3.2 mapType 地图类型

类型 说明
STANDARD 标准矢量地图(本项目使用)
SATELLITE 卫星图
TERRAIN 地形图
NONE 空白底图

3.3 myLocationControlsEnabled

设为 true 会在地图右下角显示一个"定位到我的位置"按钮(蓝色圆点按钮),用户点击后地图自动跳到当前位置。

四、初始化回调(Callback)

这是 MapKit 最关键的部分。当地图组件创建完成后,通过回调函数返回 MapComponentController

// 定义回调
this.callback = async (err, mapController): Promise<void> => {
  // 错误处理
  if (err) {
    Logger.error('testTag', `init fail, code: ${err.code}, message: ${err.message}`);
    return;
  }

  // 获取到控制器,保存下来
  this.mapController = mapController;

  // 从这里开始,可以操作地图了
  // 1. 开启"我的位置"图层(蓝色小点显示当前位置)
  this.mapController.setMyLocationEnabled(true);

  // 2. 监听"我的位置"按钮点击
  this.mapController.on('myLocationButtonClick', () => {
    Logger.info('testTag', 'Jump to my location');
    mapUtil.moveToMyLocation(mapController).then(() => {
      this.isShow = true;
    });
  });

  // 3. 监听地图标记(Marker)点击
  this.mapController.on('markerClick', (marker) => {
    this.isShow = true;
    marker.setInfoWindowVisible(true);
    this.curMarker = marker;
    this.imageScale = 1.5;
    mapUtil.imageAnimation(marker, this.imageScale);
    mapUtil.moveToCurrentPosition(
      marker.getPosition().latitude,
      marker.getPosition().longitude,
      mapController
    );
  });

  // 4. 初始化时移动到当前位置
  mapUtil.moveToMyLocation(mapController);

  // 5. 获取当前位置坐标
  this.currentLatitude = (await mapUtil.getMyLocation()).latitude;
  this.currentLongitude = (await mapUtil.getMyLocation()).longitude;
  this.latitude = this.currentLatitude;
  this.longitude = this.currentLongitude;

  // 6. 给每个加油站添加地图标记
  this.stationInfoList.forEach(async (stationItem: StationData) => {
    await mapUtil.addMapMaker(
      stationItem.latitude,
      stationItem.longitude,
      this.mapController as map.MapComponentController
    );
  });
};

提示:所有对地图的操作(移动镜头、添加标记、监听事件)都必须在 callback 被调用之后进行,因为此时 mapController 才准备好了。这是最常见的初学者踩坑点。

五、在 build() 里放置 MapComponent

build() {
  NavDestination() {
    Stack() {
      // 地图组件:占满全屏
      MapComponent({
        mapOptions: this.mapOptions,  // 传入配置
        mapCallback: this.callback,   // 传入回调(地图就绪时被调用)
      });

      // 叠加在地图上的标题栏
      this.titleBuilder();
    }
    .width(Constants.FULL_PERCENT)
    .height(Constants.FULL_PERCENT)
    // ...
  }
}

MapComponent 接受两个参数:

  • mapOptions:地图初始配置
  • mapCallback:地图就绪回调

地图组件渲染完成后,会调用 mapCallback,把 MapComponentController 传给你,你就可以开始操控地图了。

六、setMyLocationEnabled:显示当前位置

this.mapController.setMyLocationEnabled(true);

调用这个方法后,地图上会在用户当前位置显示一个蓝色光晕圆点,实时更新用户位置。

这个功能需要定位权限,所以必须在权限申请成功后才有效(项目在主页进入时就申请了权限,所以这里通常已经有权限了)。

七、地图事件监听

7.1 监听"我的位置"按钮点击

this.mapController.on('myLocationButtonClick', () => {
  Logger.info('testTag', 'Jump to my location');
  mapUtil.moveToMyLocation(mapController).then(() => {
    this.isShow = true;  // 显示底部加油站列表
  });
});

用户点击地图右下角的定位按钮,地图镜头自动跳到用户位置,同时显示底部列表。

7.2 监听 Marker 点击

this.mapController.on('markerClick', (marker) => {
  this.isShow = true;
  marker.setInfoWindowVisible(true);  // 显示标记的信息窗口
  this.curMarker = marker;            // 记录当前点击的标记
  this.imageScale = 1.5;             // 放大比例
  mapUtil.imageAnimation(marker, this.imageScale);  // 播放放大动画
  mapUtil.moveToCurrentPosition(
    marker.getPosition().latitude,
    marker.getPosition().longitude,
    mapController
  );
});

用户点击地图上的加油站标记时:

  1. 显示底部列表(isShow = true
  2. 显示标记的信息窗口
  3. 记录当前标记(用于后续取消动画)
  4. 播放标记放大动画(视觉反馈)
  5. 地图镜头移动到该标记位置

八、MapKit 事件类型

mapController.on(event, callback) 支持的常用事件:

事件 触发时机
markerClick 点击 Marker 标记
mapClick 点击地图空白区域
myLocationButtonClick 点击"我的位置"按钮
cameraChange 地图镜头位置改变
cameraChangeFinish 地图镜头停止移动
mapLoad 地图底图加载完成

九、完整流程图

onWillAppear()
  └─> init()
       ├─> STATION_LIST → stationInfoList
       ├─> 创建 mapOptions(初始配置)
       └─> 创建 callback(等待地图就绪)

MapComponent 渲染
  └─> 地图就绪,调用 callback(null, mapController)
       ├─> 保存 mapController
       ├─> setMyLocationEnabled(true)     // 显示位置蓝点
       ├─> on('myLocationButtonClick')    // 监听定位按钮
       ├─> on('markerClick')             // 监听标记点击
       ├─> moveToMyLocation()            // 定位到当前位置
       ├─> getMyLocation()              // 获取坐标(用于距离计算)
       └─> forEach → addMapMaker()      // 为每个加油站添加标记

总结

MapKit 接入的核心流程:

  1. 声明变量mapOptionsmapControllercallback
  2. 配置 mapOptions:设置初始位置、缩放级别、地图类型
  3. 定义 callback:地图就绪后获取 controller,做所有初始化操作
  4. 放置 MapComponent:传入 options 和 callback
  5. 在 callback 里操控地图:监听事件、移动镜头、添加标记

下一篇讲坐标系转换——WGS84(GPS坐标)和 GCJ02(国内地图坐标)有什么区别,为什么需要转换。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐