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 中已声明 LOCATIONAPPROXIMATELY_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. 配置签名信息

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

相关推荐
SuperEugene2 小时前
Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇
前端·javascript·vue.js·前端框架·axios
行走的陀螺仪2 小时前
手写 Vue3 极简 i18n
前端·javascript·vue.js·国际化·i18n
羽沢312 小时前
一篇简单的STOMP教程QAQ
前端·javascript·stomp
Kel2 小时前
深入 OpenAI Node SDK:一个请求的奇幻漂流
javascript·人工智能·架构
子兮曰2 小时前
AI写代码坑了90%程序员!这5个致命bug,上线就炸(附避坑清单)
前端·javascript·后端
终端鹿3 小时前
Vue3 核心 API 补充解析:toRef / toRefs / unref / isRef
前端·javascript·vue.js
英俊潇洒美少年3 小时前
vue的事件循环
前端·javascript·vue.js
GISer_Jing3 小时前
Next.js全栈开发实战与面试指南
前端·javascript·react.js
im_AMBER3 小时前
万字长文:从零实现 JWT 鉴权
前端·react.js·express