ReactNative项目OpenHarmony三方库集成实战:react-native-maps

欢迎加入开源鸿蒙跨平台社区 :https://openharmonycrossplatform.csdn.net

📋 前言

地图功能已经成为许多应用的核心特性。无论是出行导航、位置分享、还是周边服务搜索,地图都扮演着重要角色。react-native-maps 是 React Native 生态中最流行的地图组件库,提供了丰富的地图渲染和交互功能,让开发者能够快速集成地图能力。

🎯 库简介

基本信息

  • 库名称 : react-native-maps
  • 版本信息 :
    • 1.10.4 + @react-native-ohos/react-native-maps: 支持 RN 0.72 版本
    • 1.24.4 + @react-native-ohos/react-native-maps: 支持 RN 0.77 版本
  • 官方仓库: https://github.com/react-native-oh-library/react-native-maps
  • 主要功能 :
    • 🗺️ 地图显示与交互
    • 📍 标记点(Marker)支持
    • 🔵 圆形、多边形、折线覆盖物
    • 🎯 地图相机控制
    • 📱 支持 HarmonyOS 华为地图服务

为什么选择 react-native-maps?

特性 原生地图开发 react-native-maps
跨平台一致性 ❌ 需分别开发 ✅ 统一 API
开发效率 ⚠️ 较低 ✅ 快速集成
维护成本 ⚠️ 较高 ✅ 社区维护
HarmonyOS 支持 ❌ 需自行适配 ✅ 官方适配版本
TypeScript 支持 ⚠️ 需自行定义 ✅ 完整类型定义

支持的组件

组件 说明 HarmonyOS 支持
MapView 地图容器组件 ✅
Marker 标记点 ✅
Circle 圆形覆盖物 ✅
Polygon 多边形覆盖物 ✅
Polyline 折线覆盖物 ✅
Callout 信息弹窗 ✅
UrlTile 瓦片图层 ✅
WMSTile WMS 瓦片图层 ✅
Overlay 图片覆盖物 ✅
Geojson GeoJSON 支持 ✅
Cluster 标记聚合 ✅

兼容性验证

在以下环境验证通过:

  • RNOH : 0.72.90; SDK : HarmonyOS 6.0.0 Release SDK; IDE : DevEco Studio 6.0.2; ROM: 6.0.0

⚠️ 重要提示:华为地图服务申请

🔴 注意 :使用 react-native-maps 的 HarmonyOS 版本需要先开通华为地图服务(Map Kit)。未开通地图服务将导致地图无法正常显示!

为什么需要开通地图服务?

华为地图服务(Map Kit)是云端服务,需要通过华为服务器进行鉴权:

复制代码
应用 → 请求地图数据 → 华为服务器 → 验证身份 → 返回地图数据

这与 Google Maps 需要 Google Cloud Console 配置 API Key 是类似的原理。

版本差异说明

根据您的开发环境版本,配置流程有所不同:

环境版本 配置复杂度 是否需要 Client ID 是否需要公钥指纹
HarmonyOS 5.0.2+ 且 DevEco Studio 6.0.0+ ✅ 简单 ❌ 不需要 ❌ 不需要
HarmonyOS 5.0.2 以下 或 DevEco Studio 6.0.0 以下 ⚠️ 复杂 ✅ 需要 ✅ 需要

🚀 方案一:HarmonyOS 5.0.2+ 且 DevEco Studio 6.0.0+(推荐使用,我用的也是这个)

适用条件:HarmonyOS SDK 5.0.2(14) 及以上版本,且 DevEco Studio 6.0.0 Beta5 及以上版本

第一步:注册华为开发者账号

  1. 访问 华为开发者联盟
  2. 点击"注册"按钮,完成账号注册
  3. 完成实名认证(个人或企业)

第二步:通过 DevEco Studio 自动开通地图服务

🎉 优势:DevEco Studio 6.0.0+ 支持一键开通地图服务,无需手动配置 Client ID 和公钥指纹!

1. 配置签名

  1. 打开 DevEco Studio
  2. 选择 File → Project Structure
  3. 进入 Project → Signing Configs 页面
  4. 勾选 Automatically generate signature
  5. 登录华为开发者账号
  6. 点击 OK 完成签名配置

2. 开通地图服务

如果bundle name不能申请,说名称已经存在了,就换一个不存在的。我改完以后,app.json5自动同步了bundle名称。

  1. 在 Signing Configs 页面
  2. 点击 Enable open capabilities 按钮
  3. 在弹出的服务列表中,勾选 Map Kit
  4. 点击 OK 完成配置

✅ DevEco Studio 会自动完成以下操作:

  • 在 AppGallery Connect 创建/关联项目和应用
  • 开通地图服务权限
  • 配置签名信息
  • 生成并下载 Profile 文件

第三步:安装依赖

在项目根目录执行以下命令:

bash 复制代码
# RN 0.72 版本
npm install @react-native-ohos/react-native-maps@1.10.4-rc.1

# RN 0.77 版本
npm install @react-native-ohos/react-native-maps@1.24.4-rc.1

第四步:配置权限

1. 在 module.json5 中添加权限

打开 harmony/entry/src/main/module.json5,在 requestPermissions 中添加:

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:internet_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

2. 添加权限说明字符串

打开 harmony/entry/src/main/resources/base/element/string.json,添加:

json 复制代码
{
  "string": [
    {
      "name": "internet_reason",
      "value": "用于加载地图数据"
    },
    {
      "name": "location_reason",
      "value": "用于显示您的位置和导航"
    }
  ]
}

第五步:原生代码配置

1. 添加依赖

打开 harmony/entry/oh-package.json5,添加依赖:

json5 复制代码
{
  "dependencies": {
    "@rnoh/react-native-openharmony": "0.72.90",
    "@react-native-ohos/react-native-maps": "file:../../node_modules/@react-native-ohos/react-native-maps/harmony/maps.har"
  }
}

2. 同步依赖

点击 DevEco Studio 右上角的 Sync Now 按钮,或执行:

bash 复制代码
cd harmony/entry
ohpm install

3. 配置 CMakeLists.txt

打开 harmony/entry/src/main/cpp/CMakeLists.txt,添加 Maps 模块:

c 复制代码
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")
set(LOG_VERBOSITY_LEVEL 1)
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
set(WITH_HITRACE_SYSTRACE 1)
add_compile_definitions(WITH_HITRACE_SYSTRACE)

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# 添加 Maps 模块
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-maps/src/main/cpp" ./maps)

file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")

add_library(rnoh_app SHARED
    ${GENERATED_CPP_FILES}
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)

# 链接 Maps 库
+ target_link_libraries(rnoh_app PUBLIC rnoh_maps)

4. 修改 PackageProvider.cpp

打开 harmony/entry/src/main/cpp/PackageProvider.cpp,添加:

cpp 复制代码
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
+ #include "MapsPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
        std::make_shared<RNOHGeneratedPackage>(ctx),
        + std::make_shared<MapsPackage>(ctx),
    };
}

5. 在 ArkTS 侧引入组件

打开 harmony/entry/src/main/ets/pages/Index.ets(或 LoadBundle.ets),添加:

typescript 复制代码
import {
  AIRMap,
  AIR_MAP_TYPE,
  AIRMapMarker,
  AIR_MAP_MARKER_TYPE,
  AIRMapPolyline,
  AIR_MAP_POLYLINE_TYPE,
  AIRMapPolygon,
  AIR_MAP_POLYGON_TYPE,
  AIRMapCircle,
  AIR_MAP_CIRCLE_TYPE,
  AIRMapCallout,
  AIR_MAP_CALLOUT_TYPE,
  AIRMapCalloutSubview,
  AIR_MAP_CALLOUT_SUBVIEW_TYPE,
  Geojson,
  AIR_GEOJSON_TYPE,
  AIRMapUrlTile,
  AIR_URLTILE_TYPE,
  AIRMapWMSTile,
  AIR_WMSTILE_TYPE,
  AIRMapOverlay,
  AIR_OVERLAY_TYPE,
  AIRMapCluster,
  AIR_MAP_CLUSTER_TYPE,
} from "@react-native-ohos/react-native-maps"

@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {
  if (ctx.componentName === AIR_MAP_TYPE) {
    AIRMap({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_MARKER_TYPE) {
    AIRMapMarker({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_POLYLINE_TYPE) {
    AIRMapPolyline({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_POLYGON_TYPE) {
    AIRMapPolygon({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CIRCLE_TYPE) {
    AIRMapCircle({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CALLOUT_TYPE) {
    AIRMapCallout({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CALLOUT_SUBVIEW_TYPE) {
    AIRMapCalloutSubview({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_GEOJSON_TYPE) {
    Geojson({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_URLTILE_TYPE) {
    AIRMapUrlTile({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_WMSTILE_TYPE) {
    AIRMapWMSTile({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_OVERLAY_TYPE) {
    AIRMapOverlay({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CLUSTER_TYPE) {
    AIRMapCluster({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  }
}

const arkTsComponentNames: Array<string> = [
  AIR_MAP_TYPE,
  AIR_MAP_MARKER_TYPE,
  AIR_MAP_POLYLINE_TYPE,
  AIR_MAP_POLYGON_TYPE,
  AIR_MAP_CIRCLE_TYPE,
  AIR_MAP_CALLOUT_TYPE,
  AIR_MAP_CALLOUT_SUBVIEW_TYPE,
  AIR_GEOJSON_TYPE,
  AIR_URLTILE_TYPE,
  AIR_WMSTILE_TYPE,
  AIR_OVERLAY_TYPE,
  AIR_MAP_CLUSTER_TYPE,
]

6. 引入 MapsPackage

打开 harmony/entry/src/main/ets/RNPackagesFactory.ts,添加:

typescript 复制代码
import type { RNPackageContext, RNPackage } from 'rnoh/ts';
import { MapsPackage } from '@react-native-ohos/react-native-maps/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new MapsPackage(ctx),
  ];
}

第六步:运行项目

  1. 连接 HarmonyOS 设备或启动模拟器
  2. 点击 DevEco Studio 的运行按钮
  3. 地图应该正常显示

⚠️ 方案二:HarmonyOS 5.0.2 以下 或 DevEco Studio 6.0.0 以下(不推荐,要适配RN0.7x,还是用最新版)

适用条件:HarmonyOS SDK 5.0.2(14) 以下版本,或 DevEco Studio 6.0.0 Beta5 以下版本
⚠️ 注意:此方案需要手动配置 Client ID 和公钥指纹,流程较为复杂。建议升级到新版本以简化配置。

第一步:注册华为开发者账号

  1. 访问 华为开发者联盟
  2. 点击"注册"按钮,完成账号注册
  3. 完成实名认证(个人或企业)

第二步:在 AppGallery Connect 创建项目和应用

1. 登录 AppGallery Connect

访问 AppGallery Connect 并登录

2. 创建项目

  1. 点击"我的项目"
  2. 点击"创建项目"
  3. 填写项目名称,选择项目类型
  4. 点击"确定"完成创建

3. 创建应用

  1. 在项目详情页,点击"添加应用"
  2. 选择应用平台:HarmonyOS
  3. 填写应用信息:
    • 应用名称:您的应用名称
    • 应用包名 :必须与项目 app.json5 中的 bundleName 一致
    • 应用类型:选择"APP"
  4. 点击"确定"完成创建

⚠️ 重要 :应用包名必须与 HarmonyOS 项目中的 bundleName 完全一致!

第三步:开通地图服务

  1. 登录 AppGallery Connect
  2. 进入项目详情页
  3. 选择 开发与服务 → API 管理
  4. 找到 地图服务 开关,打开开关
  5. 确认已开启"地图服务"

第四步:配置签名证书

🔴 关键步骤:开通地图服务后,必须重新申请 Profile 文件!

1. 生成签名证书

在 DevEco Studio 中:

  1. 选择 Build → Generate Key and CSR
  2. 填写证书信息:
    • Key Alias: 密钥别名(如:key0)
    • Password: 密钥密码
    • Validity: 有效期(建议 25 年)
    • First and Last Name: 姓名
    • Organization: 组织名称
  3. 选择保存路径,生成 .p12 文件和 .csr 文件

2. 申请调试证书

  1. 登录 AppGallery Connect
  2. 进入 用户与访问 → 证书管理
  3. 点击"新增证书"
  4. 选择证书类型:调试证书
  5. 上传 .csr 文件
  6. 点击"提交",下载 .cer 证书文件

3. 申请 Profile 文件

  1. 进入 应用信息 → HAP Provision Profile 管理
  2. 点击"添加"
  3. 选择类型:调试 Profile
  4. 选择已申请的调试证书
  5. 点击"提交",下载 .p7b Profile 文件

⚠️ 重要:开通地图服务后,必须重新申请 Profile 文件,否则地图无法正常显示!

第五步:获取并配置 Client ID

1. 获取 Client ID

  1. 在 AppGallery Connect 项目详情页
  2. 进入 项目设置 → 常规
  3. 找到应用的 Client ID(客户端 ID)
  4. 复制此 ID

2. 配置 Client ID

打开 harmony/entry/src/main/module.json5,在 metadata 中添加 Client ID:

json5 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "metadata": [
      {
        "name": "client_id",
        "value": "110168601"  // 替换为您在 AGC 获取的 Client ID
      }
    ]
  }
}

第六步:配置应用包名

打开 harmony/AppScope/app.json5,确保 bundleName 与 AppGallery Connect 中创建的应用包名一致:

json5 复制代码
{
  "app": {
    "bundleName": "com.example.yourapp",  // 必须与 AGC 中的应用包名一致
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:app_icon",
    "label": "$string:app_name"
  }
}

第七步:配置签名

打开 harmony/build-profile.json5,配置签名信息:

json5 复制代码
{
  "app": {
    "signingConfigs": [
      {
        "name": "default",
        "type": "HarmonyOS",
        "material": {
          "certpath": "E:/certs/yourapp.cer",           // 调试证书路径
          "storePassword": "0000001F46D5FEE28...",      // 密钥库密码
          "keyAlias": "key0",                           // 密钥别名
          "keyPassword": "0000001F2B30EA30...",         // 密钥密码
          "profile": "E:/certs/yourappDebug.p7b",       // Profile 文件路径
          "signAlg": "SHA256withECDSA",
          "storeFile": "E:/certs/yourapp.p12"           // 密钥库文件路径
        }
      }
    ]
  }
}

第八步:安装依赖

在项目根目录执行以下命令:

bash 复制代码
# RN 0.72 版本
npm install @react-native-ohos/react-native-maps@1.10.4-rc.1

# RN 0.77 版本
npm install @react-native-ohos/react-native-maps@1.24.4-rc.1

第九步:配置权限

1. 在 module.json5 中添加权限

打开 harmony/entry/src/main/module.json5,在 requestPermissions 中添加:

json5 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:internet_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

2. 添加权限说明字符串

打开 harmony/entry/src/main/resources/base/element/string.json,添加:

json 复制代码
{
  "string": [
    {
      "name": "internet_reason",
      "value": "用于加载地图数据"
    },
    {
      "name": "location_reason",
      "value": "用于显示您的位置和导航"
    }
  ]
}

第十步:原生代码配置

1. 添加依赖

打开 harmony/entry/oh-package.json5,添加依赖:

json5 复制代码
{
  "dependencies": {
    "@rnoh/react-native-openharmony": "0.72.90",
    "@react-native-ohos/react-native-maps": "file:../../node_modules/@react-native-ohos/react-native-maps/harmony/maps.har"
  }
}

2. 同步依赖

点击 DevEco Studio 右上角的 Sync Now 按钮,或执行:

bash 复制代码
cd harmony/entry
ohpm install

3. 配置 CMakeLists.txt

打开 harmony/entry/src/main/cpp/CMakeLists.txt,添加 Maps 模块:

cmake 复制代码
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")
set(LOG_VERBOSITY_LEVEL 1)
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
set(WITH_HITRACE_SYSTRACE 1)
add_compile_definitions(WITH_HITRACE_SYSTRACE)

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# 添加 Maps 模块
add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-maps/src/main/cpp" ./maps)

file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")

add_library(rnoh_app SHARED
    ${GENERATED_CPP_FILES}
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)

# 链接 Maps 库
target_link_libraries(rnoh_app PUBLIC rnoh_maps)

4. 修改 PackageProvider.cpp

打开 harmony/entry/src/main/cpp/PackageProvider.cpp,添加:

cpp 复制代码
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
#include "MapsPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
        std::make_shared<RNOHGeneratedPackage>(ctx),
        std::make_shared<MapsPackage>(ctx),
    };
}

5. 在 ArkTS 侧引入组件

打开 harmony/entry/src/main/ets/pages/Index.ets(或 LoadBundle.ets),添加:

typescript 复制代码
import {
  AIRMap,
  AIR_MAP_TYPE,
  AIRMapMarker,
  AIR_MAP_MARKER_TYPE,
  AIRMapPolyline,
  AIR_MAP_POLYLINE_TYPE,
  AIRMapPolygon,
  AIR_MAP_POLYGON_TYPE,
  AIRMapCircle,
  AIR_MAP_CIRCLE_TYPE,
  AIRMapCallout,
  AIR_MAP_CALLOUT_TYPE,
  AIRMapCalloutSubview,
  AIR_MAP_CALLOUT_SUBVIEW_TYPE,
  Geojson,
  AIR_GEOJSON_TYPE,
  AIRMapUrlTile,
  AIR_URLTILE_TYPE,
  AIRMapWMSTile,
  AIR_WMSTILE_TYPE,
  AIRMapOverlay,
  AIR_OVERLAY_TYPE,
  AIRMapCluster,
  AIR_MAP_CLUSTER_TYPE,
} from "@react-native-ohos/react-native-maps"

@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {
  if (ctx.componentName === AIR_MAP_TYPE) {
    AIRMap({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_MARKER_TYPE) {
    AIRMapMarker({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_POLYLINE_TYPE) {
    AIRMapPolyline({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_POLYGON_TYPE) {
    AIRMapPolygon({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CIRCLE_TYPE) {
    AIRMapCircle({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CALLOUT_TYPE) {
    AIRMapCallout({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CALLOUT_SUBVIEW_TYPE) {
    AIRMapCalloutSubview({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_GEOJSON_TYPE) {
    Geojson({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_URLTILE_TYPE) {
    AIRMapUrlTile({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_WMSTILE_TYPE) {
    AIRMapWMSTile({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_OVERLAY_TYPE) {
    AIRMapOverlay({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  } else if (ctx.componentName === AIR_MAP_CLUSTER_TYPE) {
    AIRMapCluster({
      ctx: ctx.rnComponentContext,
      tag: ctx.tag,
    })
  }
}

const arkTsComponentNames: Array<string> = [
  AIR_MAP_TYPE,
  AIR_MAP_MARKER_TYPE,
  AIR_MAP_POLYLINE_TYPE,
  AIR_MAP_POLYGON_TYPE,
  AIR_MAP_CIRCLE_TYPE,
  AIR_MAP_CALLOUT_TYPE,
  AIR_MAP_CALLOUT_SUBVIEW_TYPE,
  AIR_GEOJSON_TYPE,
  AIR_URLTILE_TYPE,
  AIR_WMSTILE_TYPE,
  AIR_OVERLAY_TYPE,
  AIR_MAP_CLUSTER_TYPE,
]

6. 引入 MapsPackage

打开 harmony/entry/src/main/ets/RNPackagesFactory.ts,添加:

typescript 复制代码
import type { RNPackageContext, RNPackage } from 'rnoh/ts';
import { MapsPackage } from '@react-native-ohos/react-native-maps/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new MapsPackage(ctx),
  ];
}

第十一步:运行项目

  1. 连接 HarmonyOS 设备或启动模拟器
  2. 点击 DevEco Studio 的运行按钮
  3. 地图应该正常显示

📖 API 详解

🔷 MapView - 地图容器

MapView 是地图的核心容器组件,用于显示地图和控制地图状态。

typescript 复制代码
import MapView from 'react-native-maps';

<MapView
  style={{ flex: 1 }}
  initialRegion={{
    latitude: 39.9042,
    longitude: 116.4074,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  }}
/>

主要属性

属性 类型 必填 说明 HarmonyOS 支持
initialRegion Region ❌ 初始显示区域 ✅
region Region ❌ 当前显示区域(受控) ✅
camera Camera ❌ 相机配置 ✅
showsUserLocation boolean ❌ 显示用户位置 ✅
showsCompass boolean ❌ 显示指南针 ✅
showsScale boolean ❌ 显示比例尺 ✅
zoomEnabled boolean ❌ 启用缩放 ✅
scrollEnabled boolean ❌ 启用滚动 ✅
rotateEnabled boolean ❌ 启用旋转 ✅
mapType string ❌ 地图类型 ✅

Region 类型

typescript 复制代码
interface Region {
  latitude: number;      // 纬度
  longitude: number;     // 经度
  latitudeDelta: number; // 纬度跨度
  longitudeDelta: number; // 经度跨度
}

事件回调

事件 说明 HarmonyOS 支持
onRegionChange 区域变化时触发 ✅
onRegionChangeComplete 区域变化完成时触发 ✅
onPress 点击地图时触发 ✅
onLongPress 长按地图时触发 ✅
onMarkerPress 点击标记时触发 ✅

🔷 Marker - 标记点

Marker 用于在地图上标注位置。

typescript 复制代码
import MapView, { Marker } from 'react-native-maps';

<MapView style={{ flex: 1 }}>
  <Marker
    coordinate={{
      latitude: 39.9042,
      longitude: 116.4074,
    }}
    title="北京"
    description="中国首都"
  />
</MapView>

主要属性

属性 类型 必填 说明 HarmonyOS 支持
coordinate LatLng ✅ 标记坐标 ✅
title string ❌ 标题 ✅
description string ❌ 描述 ✅
image ImageSource ❌ 自定义图标 ✅
opacity number ❌ 透明度 ✅
draggable boolean ❌ 可拖拽 ✅
anchor Point ❌ 锚点 ✅

自定义标记图标

typescript 复制代码
<Marker
  coordinate={{ latitude: 39.9042, longitude: 116.4074 }}
  image={require('./assets/marker.png')}
/>

🔷 Circle - 圆形覆盖物

Circle 用于在地图上绘制圆形区域。

typescript 复制代码
import MapView, { Circle } from 'react-native-maps';

<MapView style={{ flex: 1 }}>
  <Circle
    center={{
      latitude: 39.9042,
      longitude: 116.4074,
    }}
    radius={1000}
    fillColor="rgba(255, 0, 0, 0.3)"
    strokeColor="rgba(255, 0, 0, 1)"
    strokeWidth={2}
  />
</MapView>

主要属性

属性 类型 必填 说明 HarmonyOS 支持
center LatLng ✅ 圆心坐标 ✅
radius number ✅ 半径(米) ✅
fillColor string ❌ 填充颜色 ✅
strokeColor string ❌ 边框颜色 ✅
strokeWidth number ❌ 边框宽度 ✅

🔷 Polygon - 多边形覆盖物

Polygon 用于在地图上绘制多边形区域。

typescript 复制代码
import MapView, { Polygon } from 'react-native-maps';

<MapView style={{ flex: 1 }}>
  <Polygon
    coordinates={[
      { latitude: 39.91, longitude: 116.40 },
      { latitude: 39.90, longitude: 116.41 },
      { latitude: 39.89, longitude: 116.40 },
      { latitude: 39.90, longitude: 116.39 },
    ]}
    fillColor="rgba(0, 200, 0, 0.5)"
    strokeColor="rgba(0, 200, 0, 1)"
    strokeWidth={2}
  />
</MapView>

主要属性

属性 类型 必填 说明 HarmonyOS 支持
coordinates LatLng\[\] ✅ 顶点坐标数组 ✅
fillColor string ❌ 填充颜色 ✅
strokeColor string ❌ 边框颜色 ✅
strokeWidth number ❌ 边框宽度 ✅
holes LatLng\[\]\[\] ❌ 空洞区域 ✅

🔷 Polyline - 折线覆盖物

Polyline 用于在地图上绘制路线或路径。

typescript 复制代码
import MapView, { Polyline } from 'react-native-maps';

<MapView style={{ flex: 1 }}>
  <Polyline
    coordinates={[
      { latitude: 39.91, longitude: 116.40 },
      { latitude: 39.90, longitude: 116.41 },
      { latitude: 39.89, longitude: 116.42 },
    ]}
    strokeColor="#FF0000"
    strokeWidth={3}
    lineDashPattern={[5, 2, 3, 2]}
  />
</MapView>

主要属性

属性 类型 必填 说明 HarmonyOS 支持
coordinates LatLng\[\] ✅ 端点坐标数组 ✅
strokeColor string ❌ 线条颜色 ✅
strokeWidth number ❌ 线条宽度 ✅
lineDashPattern number\[\] ❌ 虚线模式 ✅
geodesic boolean ❌ 测地线 ✅

📱 完整示例

typescript 复制代码
import React, { useState, useRef } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Alert,
} from 'react-native';
import MapView, {
  Marker,
  PROVIDER_DEFAULT,
} from '@react-native-ohos/react-native-maps';

const LATITUDE_DELTA = 0.01;
const LONGITUDE_DELTA = 0.01;

const DEFAULT_LOCATION = {
  latitude: 39.9042,
  longitude: 116.4074,
};

interface Coordinate {
  latitude: number;
  longitude: number;
}

const MapDemo = () => {
  const mapRef = useRef<MapView>(null);
  const [region, setRegion] = useState<Coordinate>(DEFAULT_LOCATION);
  const [markers, setMarkers] = useState<Coordinate[]>([]);

  const handleMapPress = (e: any) => {
    const coordinate = e.nativeEvent?.coordinate;
    if (coordinate) {
      setMarkers([...markers, coordinate]);
    }
  };

  const handleMarkerPress = (index: number) => {
    const marker = markers[index];
    if (marker) {
      Alert.alert(
        '标记点',
        `位置 ${index + 1}\n纬度: ${marker.latitude.toFixed(4)}\n经度: ${marker.longitude.toFixed(4)}`
      );
    }
  };

  const handleRegionChangeComplete = (newRegion: any) => {
    if (newRegion && typeof newRegion.latitude === 'number' && typeof newRegion.longitude === 'number') {
      setRegion({
        latitude: newRegion.latitude,
        longitude: newRegion.longitude,
      });
    }
  };

  const clearMarkers = () => {
    setMarkers([]);
  };

  return (
    <View style={styles.container}>
      <MapView
        ref={mapRef}
        style={styles.map}
        provider={PROVIDER_DEFAULT}
        initialRegion={{
          ...DEFAULT_LOCATION,
          latitudeDelta: 0.1,
          longitudeDelta: 0.1,
        }}
        onRegionChangeComplete={handleRegionChangeComplete}
        onPress={handleMapPress}
        showsUserLocation
        showsCompass
        showsScale
        showsMyLocationButton
        zoomEnabled
        scrollEnabled
        rotateEnabled
      >
        {markers.map((marker, index) => (
          <Marker
            key={index}
            coordinate={marker}
            title={`标记 ${index + 1}`}
            description="点击查看详情"
            onPress={() => handleMarkerPress(index)}
          />
        ))}
      </MapView>

      <View style={styles.topPanel}>
        <View style={styles.infoBox}>
          <Text style={styles.infoText}>点击地图添加标记点</Text>
          <Text style={styles.infoText}>当前标记数: {markers.length}</Text>
        </View>
        <TouchableOpacity style={styles.clearButton} onPress={clearMarkers}>
          <Text style={styles.clearButtonText}>清除标记</Text>
        </TouchableOpacity>
      </View>

      <View style={styles.coordinateBox}>
        <Text style={styles.coordinateText}>
          纬度: {region?.latitude?.toFixed(4) ?? 'N/A'}
        </Text>
        <Text style={styles.coordinateText}>
          经度: {region?.longitude?.toFixed(4) ?? 'N/A'}
        </Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
  topPanel: {
    position: 'absolute',
    top: 20,
    left: 20,
    right: 20,
    backgroundColor: 'rgba(255, 255, 255, 0.95)',
    borderRadius: 12,
    padding: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  infoBox: {
    marginBottom: 8,
  },
  infoText: {
    fontSize: 14,
    color: '#333',
    marginBottom: 2,
  },
  clearButton: {
    backgroundColor: '#2196F3',
    paddingVertical: 10,
    borderRadius: 8,
    alignItems: 'center',
  },
  clearButtonText: {
    color: '#fff',
    fontSize: 14,
    fontWeight: '600',
  },
  coordinateBox: {
    position: 'absolute',
    bottom: 120,
    left: 20,
    backgroundColor: 'rgba(0, 0, 0, 0.7)',
    borderRadius: 8,
    padding: 12,
  },
  coordinateText: {
    color: '#fff',
    fontSize: 12,
    fontFamily: 'monospace',
  },
});

export default MapDemo;

❓ 常见问题

1. 地图无法显示(白屏)

问题:运行应用后地图显示为空白。

解决方案:

  1. ✅ 确认已在 AppGallery Connect 开通地图服务
  2. ✅ 确认 Client ID 配置正确(仅旧版本需要)
  3. ✅ 确认应用包名与 AGC 中一致
  4. ✅ 确认签名配置正确(使用开通地图服务后重新申请的 Profile)
  5. ✅ 确认网络连接正常

2. 地图显示"鉴权失败"

问题:地图显示鉴权失败错误。

解决方案:

  1. 检查 Client ID 是否正确配置在 module.json5 中(仅旧版本需要)
  2. 确认签名证书指纹已在 AGC 中配置(仅旧版本需要)
  3. 确认 Profile 文件是在开通地图服务后重新申请的

3. 定位权限申请失败

问题:申请定位权限时失败。

解决方案:

  1. 确认 module.json5 中已声明 LOCATION 和 APPROXIMATELY_LOCATION 权限
  2. 确认 string.json 中已添加权限说明
  3. 检查设备是否开启了定位服务

4. 标记点不显示

问题:添加的 Marker 不显示。

解决方案:

  1. 检查坐标是否在可视区域内
  2. 检查 Marker 的 coordinate 属性是否正确
  3. 确认 MapView 组件已正确渲染

📚 参考资料


✅ 总结

react-native-maps 是一个功能强大的地图组件库,在 HarmonyOS 平台上需要配合华为地图服务(Map Kit)使用。

版本选择建议

您的环境 推荐方案
HarmonyOS 5.0.2+ 且 DevEco Studio 6.0.0+ ✅ 方案一:自动配置,简单快捷
其他版本 ⚠️ 方案二:手动配置,流程较多

关键步骤总结

方案一(新版本):

  1. DevEco Studio 自动签名
  2. Enable open capabilities → 勾选 Map Kit
  3. 完成!

方案二(旧版本):

  1. AppGallery Connect 创建项目和应用
  2. 开通地图服务
  3. 生成签名证书
  4. 申请调试证书和 Profile
  5. 获取并配置 Client ID
  6. 配置签名信息

关键提醒:使用地图功能前,务必先开通地图服务!

相关推荐
linux_cfan22 分钟前
videojs v10 源代码系列解读:35 · HLS 解析器:multivariant 与 media playlist
前端·javascript·音视频
web打印社区25 分钟前
汽修 / 4S 店:维修工单、结算单网页怎么静默出纸
开发语言·前端·javascript·chrome·ecmascript
研☆香14 小时前
jQuery + Swiper.js 实现动态渲染轮播图
前端·javascript·jquery
特立独行的猫A18 小时前
仓颉版 Tauri:基于轻量化线程与纯血鸿蒙架构的下一代 Web 混合开发利器
javascript
李游Leo19 小时前
HarmonyOS 7 ArkTS + ohpm:三方 JS 库副作用隔离与 API 26 升级回归【鸿蒙心迹】
javascript·回归·harmonyos
你挚爱的强哥19 小时前
【sgKeyboard】自定义组件:虚拟键盘
前端·javascript·计算机外设
锅里游的鱼吖20 小时前
echarts自定义折线图
前端·javascript·数据库
何何____20 小时前
Vue 全局事件总线详解
前端·javascript·vue.js
u01110267521 小时前
工具页案例图如何编写 alt让图片说明与处理场景对应
java·前端·javascript·图像处理·人工智能·算法·ai作画
事圆则缓21 小时前
Flutter 状态管理框架对比(五):GetX 的响应式、依赖注册与生命周期
前端·javascript·flutter