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中的高德地圖適配鴻蒙