HarmonyOS NEXT 适配高德地图FlutterSDK实现地图展示,添加覆盖物和移动Camera

HarmonyOS NEXT 适配高德地图 Flutter SDK 实现地图展示,添加覆盖物和移动 Camera

在现代移动应用开发中,地图功能是许多应用的核心组成部分之一。HarmonyOS NEXT 提供了强大的跨平台开发能力,而高德地图 Flutter SDK 则为开发者提供了丰富的地图功能。因为高德地图FlutterSDK已停止维护,并且也没有鸿蒙测的适配库,所以才有了下面的内容,本文将详细介绍如何在 HarmonyOS NEXT 中适配高德地图 Flutter SDK,实现地图展示、添加覆盖物和移动 Camera 的功能。

一、技术亮点

1.1 Flutter 的优势

  • 高效的构建效率:Flutter 的热重载特性允许开发者即时预览代码更改的影响,极大地提高了开发效率。
  • 跨平台兼容性:Flutter 应用可以在 Android、iOS 和 Web 等多个平台上运行,无需为每个平台单独开发,从而节省了开发成本。
  • 丰富的组件库:Flutter 提供了丰富的组件库,如按钮、文本框、列表等,帮助开发者轻松创建出色的用户界面。

1.2 高德地图 Flutter SDK 的优势

高德地图 Flutter SDK 提供了强大的地图功能,包括地图展示、覆盖物添加和 Camera 操作等。通过与 Flutter 的结合,开发者可以轻松实现地图相关的功能。

二、集成高德地图 SDK

首先我们的基础是要先集成高德地图的FlutterSDK

json 复制代码
amap_flutter_map: ^3.0.0

2.1 获取 SDK

首先,你需要在高德开放平台注册并获取 SDK。别忘了申请你的高德SDK的key,具体可以参考高德地图的官网文档

2.2 从ohpm仓库获取高德地图包

json 复制代码
 "dependencies": {
    "@amap/amap_lbs_common": ">=1.2.0",
    "@amap/amap_lbs_map3d": ">=2.2.0"
}

2.3 声明权限,工程的oh-package.json5文件中添加依赖

module.json5 中添加必要的权限和模块声明。

json 复制代码
{
...
  "requestPermissions": [
  {
    "name": 'ohos.permission.INTERNET',
  }
]
...

三、地图展示

3.1 接下来进入正题,既然是适配高德FlutterSDK,那肯定需要我们在鸿蒙端做一些重要工作

首先我们需要创建一个AMapView这个类的作用是接收Dart测过来的消息

typescript 复制代码
/**
 * @FileName : AMapView
 * @Author : kirk.wang
 * @Time : 2025/5/7 17:19
 * @Description :
 */
import { BinaryMessenger, MethodCall, MethodCallHandler, MethodChannel,
  MethodResult,
  StandardMethodCodec } from "@ohos/flutter_ohos";
import PlatformView, { Params } from '@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView'

import { common } from "@kit.AbilityKit";
import { AMapBuilder } from "./AMapComponent";

export class AMapView extends PlatformView implements MethodCallHandler {

  methodChannel: MethodChannel;
  args?: ESObject;
  constructor(context: common.Context, viewId: number , args: ESObject, message: BinaryMessenger) {
    super();
    this.args = args
    this.methodChannel = new MethodChannel(message, `amap_flutter_map_${viewId}`, StandardMethodCodec.INSTANCE);
    this.methodChannel.setMethodCallHandler(this);
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    // 接受Dart侧发来的消息
    let method: string = call.method;
    let link1: SubscribedAbstractProperty<number> = AppStorage.link('numValue');
    switch (method) {
      case 'getMessageFromFlutterView':
        let value: ESObject = call.args;
        link1.set(value)
        console.log("nodeController receive message from dart: ");
        result.success(true);
        break;
    }
  }

  getView(): WrappedBuilder<[Params]> {
    return new WrappedBuilder(AMapBuilder);
  }

  public sendMessage = () => {
    console.log("nodeController sendMessage")
    //向Dart侧发送消息
    this.methodChannel.invokeMethod('getMessageFromOhosView', 'natvie - ');
  }

  dispose(): void {

  }


}

3.2 这个AMapView在什么时候用呢,创建一个AMapPlatformViewFactory类继承自 PlatformViewFactory,用于创建和管理地图相关的原生视图(PlatformView)

typescript 复制代码
import { Any, BinaryMessenger, MessageCodec, PlatformView, PlatformViewFactory } from "@ohos/flutter_ohos";
import { common } from "@kit.AbilityKit";
import { AMapView } from "./AMapView";


export default class AMapPlatformViewFactory extends PlatformViewFactory {

  message: BinaryMessenger;

  constructor(message: BinaryMessenger, createArgsCodes: MessageCodec<Object>) {
    super(createArgsCodes)

    this.message = message;
  }

  public create(context: common.Context, viewId: number, args: Any): PlatformView {
    return new AMapView(context, viewId, args, this.message);
  }

}

3.3 创建地图插件,注册工厂类

typescript 复制代码
/**
 * @FileName : AMapFlutterMapPlugin
 * @Author : kirk.wang
 * @Time : 2025/5/8 10:15
 * @Description : 高德地图插件
 */
import {
  Any,
  BasicMessageChannel, FlutterPlugin, FlutterPluginBinding,
  MethodChannel,
  StandardMessageCodec} from "@ohos/flutter_ohos";
import AMapPlatformViewFactory from "./AMapPlatformViewFactory";

export default class AMapFlutterMapPlugin implements FlutterPlugin {
  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.channel?.setMethodCallHandler(null)
  }

  private channel?:MethodChannel;
  private basicChannel?: BasicMessageChannel<Any>;
  private VIEW_TYPE : string = "com.amap.flutter.map";

  getUniqueClassName(): string {
    return "AMapFlutterMapPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    binding.getPlatformViewRegistry().registerViewFactory(this.VIEW_TYPE, new AMapPlatformViewFactory(binding.getBinaryMessenger(),StandardMessageCodec.INSTANCE))
  }
}

3.3 创建地图主视图,然后根据传递的数据设置鸿蒙端的高德原生地图,在AMapView里有一个getView,就是返回的下面的视图代码

typescript 复制代码
/**
 * @FileName : AMapComponent
 * @Author : kirk.wang
 * @Time : 2025/5/8 14:20
 * @Description : 地图主视图
 */
import {
  AMap,
  BitmapDescriptorFactory,
  CameraUpdateFactory,
  LatLng,
  MapsInitializer,
  MapView,
  MapViewComponent,
  MapViewManager,
  MarkerOptions
} from '@amap/amap_lbs_map3d'
import { Params } from '@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView'
import { AMapView } from './AMapView'
import { ArrayList, HashMap, List } from '@kit.ArkTS';
import image from '@ohos.multimedia.image';
import json from '@ohos.util.json';

const key = "你在高德地图申请的鸿蒙端的key";

@Component
struct AMapComponent {
  @Prop params: Params
  customView: AMapView = this.params.platformView as AMapView
  @StorageLink('numValue') storageLink: string = "first"
  @State bkColor: Color = Color.Red
  aMap?: AMap;

  aboutToAppear(): void {
    MapsInitializer.setApiKey(key);
    MapsInitializer.setDebugMode(true);
   
    MapViewManager.getInstance().registerMapViewCreatedCallback((mapview?: MapView, mapViewName?: string) => {
      if (!mapview) {
        return;
      }
      mapview!.onCreate();
      mapview!.getMapAsync((map) => {
        this.aMap = map;
 
      })
    })
  }

  build() {

    Stack() {
      MapViewComponent({ mapViewName: "harmony_map_demo" }).zIndex(0)
    }
    .direction(Direction.Ltr)
    .width('100%')
    .height('100%')
  }
}

@Builder
export function AMapBuilder(params: Params) {
  AMapComponent({ params: params })
    .backgroundColor(Color.Yellow)
}

四、添加覆盖物

4.1 根据接收的数据来设置覆盖物,设置地图中心点以及缩放级别

在地图上添加覆盖物时,需要将 Flutter Widget 转换为图片,然后通过原生的 Marker 接口添加到地图上。查看高德Flutter插件可知发送参数的信息,也可以在鸿蒙测断点查看,注意 以下接收数据的key不可更改,否则无法接收到数据,例如:markersToAdd、initialCameraPosition等

Flutter传输的字节数组在鸿蒙端接收有问题,导致这个地方卡了好几天┭┮﹏┭┮

typescript 复制代码
  aboutToAppear(): void {
    MapsInitializer.setApiKey(key);
    MapsInitializer.setDebugMode(true);
    let tempList = this.customView.args?.get("markersToAdd") as List<Map<String, Object>>;
    let optionsList = new ArrayList<MarkerOptions>()
    try {
      tempList.forEach(async (op) => {
        let options = new MarkerOptions()
        options.setAlpha(op.get('alpha') as number);
        let anchor = op.get('anchor') as Array<number>
        options.setAnchor(anchor[0], anchor[1]);
        options.setClickable(op.get('clickable') as boolean);
        options.setDraggable(op.get('draggable') as boolean);
        options.setInfoWindowEnable(op.get('infoWindowEnable') as boolean);
        let positionList = op.get('position') as Array<number>
        if (positionList.length === 2) {
          options.setPosition(new LatLng(positionList[0], positionList[1]));
        }
        options.setZIndex(op.get('zIndex') as number);
        let icon = op.get('icon') as Array<string | Uint8Array>;
        if (icon.length >= 2) {
          try {
            //因chanel传值导致数据被破坏,无法正确识别Uint8Array参数,所以需要json转换后重新生成Uint8Array
            //将数据转成JSON字符串
            let jsonStr = json.stringify(icon[1]);
            //将JSON字符串格式化成map
            let obj = json.parse(jsonStr) as HashMap<string, number>;
            // 将对象转换为数组
            const array = Object.keys(obj).map((key): number => obj[key]);
            if (Array.isArray(array)) {
              //根据最新的数组生成Uint8Array
              let icon1 = new Uint8Array(array);
              //拷贝字节数组
              const buffer1 = icon1.buffer.slice(0);
              //通过字节数组生成图片
              let imageSource: image.ImageSource = image.createImageSource(buffer1);
              let decodingOption: image.DecodingOptions = {
                editable: true,
              }
              imageSource.createPixelMap(decodingOption)
                .then(async (pixelmap: PixelMap) => {
                  //向options添加图片信息
                  options.setIcon(BitmapDescriptorFactory.fromPixelMapSync(pixelmap));
                  //将options添加到数组
                  optionsList.add(options);
                })
            }
          } catch (error) {
            console.error('Error:', error);
          }
        }

      });
    } catch (e) {
      console.log("===============Alpha:error:" + e);
    }
    

4.2 将 Flutter Widget 添加到地图

typescript 复制代码
MapViewManager.getInstance().registerMapViewCreatedCallback((mapview?: MapView, mapViewName?: string) => {
      if (!mapview) {
        return;
      }
      mapview!.onCreate();
      mapview!.getMapAsync((map) => {
        this.aMap = map;
        //向地图添加Markers
        if (optionsList !== null && optionsList.length > 0) {
          this.aMap?.addMarkers(optionsList);
        }
      })
    })
  }

五、设置地图中心点以及缩放级别

5.1 移动 Camera

通过调用地图的 moveCamera 方法,可以移动地图的 Camera。

typescript 复制代码
  aboutToAppear(): void {
   ...
    MapViewManager.getInstance().registerMapViewCreatedCallback((mapview?: MapView, mapViewName?: string) => {
      if (!mapview) {
        return;
      }
      mapview!.onCreate();
      mapview!.getMapAsync((map) => {
        this.aMap = map;
        let cameraPosition = this.customView.args?.get("initialCameraPosition") as Map<String, Object>;
        let targetList = cameraPosition.get('target') as Array<number>
        //设置地图中心点以及缩放级别
        this.aMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(targetList[0], targetList[1]),
          cameraPosition.get('zoom') as number));
        ...
      })
    })
  }

至此鸿蒙端的开发工作至此结束

七、Flutter端处理

找到Flutter端的method_channel_amap_flutter_map.dart,里面有个buildView函数,里面有判断只支持

dart 复制代码
 @override
  Widget buildView(
      Map<String, dynamic> creationParams,
      Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
      void Function(int id) onPlatformViewCreated) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      creationParams['debugMode'] = kDebugMode;
      return AndroidView(
        viewType: VIEW_TYPE,
        onPlatformViewCreated: onPlatformViewCreated,
        gestureRecognizers: gestureRecognizers,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      );
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      return UiKitView(
        viewType: VIEW_TYPE,
        onPlatformViewCreated: onPlatformViewCreated,
        gestureRecognizers: gestureRecognizers,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      );
    } 
    // else if (defaultTargetPlatform == TargetPlatform.ohos) {
    //   return OhosView(
    //     viewType: VIEW_TYPE,
    //     onPlatformViewCreated: onPlatformViewCreated,
    //     gestureRecognizers: gestureRecognizers,
    //     creationParams: creationParams,
    //     creationParamsCodec: const StandardMessageCodec(),
    //   );
    // }
    return Text('当前平台:$defaultTargetPlatform, 不支持使用高德地图插件');
  }

将我注释掉的代码放开即可!接下来就可以在你的鸿蒙设备上调试了。完全按照我的代码,除了高德的key,其余的都不要随便更改哦,否则可能运行出来有问题

六、总结

通过上述步骤,你可以在 HarmonyOS NEXT 中适配高德地图 Flutter SDK,实现地图展示、添加覆盖物和移动 Camera 的功能。Flutter 的跨平台特性和高德地图的强大功能相结合,为开发者提供了极大的便利。

希望本文能够帮助你在 HarmonyOS NEXT 中成功集成高德地图 Flutter SDK,并实现所需的地图功能。如果你在开发过程中遇到任何问题,可以参考高德地图的官方文档,或在相关社区寻求帮助。

七、参考