博客 / 詳情

返回

HarmonyOS NEXT 適配高德地圖 Flutter SDK 實現地圖展示,添加覆蓋物和移動 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

amap_flutter_map: ^3.0.0

2.1 獲取 SDK

首先,你需要在高德開放平台註冊並獲取 SDK。別忘了申請你的高德SDK的key,具體可以參考高德地圖的官網文檔

2.2 從ohpm倉庫獲取高德地圖包

 "dependencies": {
    "@amap/amap_lbs_common": ">=1.2.0",
    "@amap/amap_lbs_map3d": ">=2.2.0"
}

2.3 聲明權限,工程的oh-package.json5文件中添加依賴

module.json5 中添加必要的權限和模塊聲明。

{
...
  "requestPermissions": [
  {
    "name": 'ohos.permission.INTERNET',
  }
]
...

三、地圖展示

3.1 接下來進入正題,既然是適配高德FlutterSDK,那肯定需要我們在鴻蒙端做一些重要工作

首先我們需要創建一個AMapView這個類的作用是接收Dart測過來的消息

/**
 * @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)

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 創建地圖插件,註冊工廠類

/**
 * @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,就是返回的下面的視圖代碼

/**
 * @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傳輸的字節數組在鴻蒙端接收有問題,導致這個地方卡了好幾天┭┮﹏┭┮

  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 添加到地圖

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。

  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函數,裏面有判斷只支持

 @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,並實現所需的地圖功能。如果你在開發過程中遇到任何問題,可以參考高德地圖的官方文檔,或在相關社區尋求幫助。

七、參考

  • Flutter中的高德地圖適配鴻蒙
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.