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,并实现所需的地图功能。如果你在开发过程中遇到任何问题,可以参考高德地图的官方文档,或在相关社区寻求帮助。