第四篇:地图标记Marker
本篇教程将学习如何在地图上添加各种标记,包括默认标记、自定义图标、自定义View标记,以及标记的交互功能。
学习目标
- 添加默认样式标记
- 使用自定义图标
- 创建自定义View标记
- 处理标记点击和拖拽事件
- 标记动画效果
1. Marker基础
Marker(标记)是地图上最常用的覆盖物,用于标注特定位置。
1.1 MarkerOptions 常用属性
| 属性 | 方法 | 说明 |
|---|---|---|
| 位置 | setPosition(LatLng) | 标记的地理坐标 |
| 标题 | setTitle(string) | 点击显示的标题 |
| 描述 | setSnippet(string) | 点击显示的描述 |
| 图标 | setIcon(BitmapDescriptor) | 自定义图标 |
| 锚点 | setAnchor(u, v) | 图标锚点位置(0-1) |
| 可拖拽 | setDraggable(boolean) | 是否可拖拽 |
| 可点击 | setClickable(boolean) | 是否可点击 |
| 层级 | setZIndex(number) | 覆盖物层级 |
| 可见性 | setVisible(boolean) | 是否可见 |
| 旋转 | setRotateAngle(angle) | 旋转角度 |
2. 完整代码示例
创建文件 entry/src/main/ets/pages/Demo03_Marker.ets:
import {
AMap,
MapView,
MapViewComponent,
MapViewManager,
MapViewCreateCallback,
LatLng,
Marker,
MarkerOptions,
BitmapDescriptor,
BitmapDescriptorFactory,
OnMarkerDragListener,
CameraUpdateFactory
} from '@amap/amap_lbs_map3d';
const MAP_VIEW_NAME = 'MarkerDemo';
// 获取应用上下文
let globalContext: Context;
/**
* 标记信息接口
*/
interface MarkerInfo {
name: string;
lat: number;
lng: number;
description: string;
}
@Entry
@Component
struct Demo03_Marker {
private mapView: MapView | undefined = undefined;
private aMap: AMap | undefined = undefined;
// 存储所有标记
private markers: Marker[] = [];
@State isMapReady: boolean = false;
@State markerInfo: string = '点击标记查看信息';
@State dragInfo: string = '';
// 示例地点数据
private places: MarkerInfo[] = [
{ name: '天安门', lat: 39.909187, lng: 116.397451, description: '北京市中心地标' },
{ name: '故宫', lat: 39.916345, lng: 116.397155, description: '世界文化遗产' },
{ name: '王府井', lat: 39.914539, lng: 116.410276, description: '著名商业街' },
{ name: '北海公园', lat: 39.924091, lng: 116.389472, description: '皇家园林' }
];
private mapViewCreateCallback: MapViewCreateCallback =
(mapview: MapView | undefined, mapViewName: string | undefined) => {
if (!mapview || mapViewName !== MAP_VIEW_NAME) return;
this.mapView = mapview;
this.mapView.onCreate();
this.mapView.getMapAsync((map: AMap) => {
this.aMap = map;
this.isMapReady = true;
// 移动到北京
const beijing = new LatLng(39.916345, 116.397155);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(beijing, 14));
// 启用缩放控件
map.getUiSettings()?.setZoomControlsEnabled(true);
// 设置标记事件监听
this.setupMarkerListeners();
});
};
/**
* 设置标记事件监听
*/
private setupMarkerListeners(): void {
if (!this.aMap) return;
// 1. 标记点击事件
this.aMap.setOnMarkerClickListener((marker: Marker): boolean => {
console.info('[Marker] Clicked:', marker.getTitle());
const title = marker.getTitle() || '未命名';
const snippet = marker.getSnippet() || '';
const position = marker.getPosition();
this.markerInfo = `${title}\n${snippet}\n坐标: ${position.latitude.toFixed(4)}, ${position.longitude.toFixed(4)}`;
// 显示InfoWindow
marker.showInfoWindow();
return true; // 返回true表示已处理,不执行默认行为
});
// 2. 标记拖拽事件
this.aMap.setOnMarkerDragListener(new OnMarkerDragListener(
// 开始拖拽
(marker: Marker) => {
console.info('[Marker] Drag start:', marker.getTitle());
this.dragInfo = `开始拖拽: ${marker.getTitle()}`;
},
// 拖拽中
(marker: Marker) => {
const pos = marker.getPosition();
this.dragInfo = `拖拽中: ${pos.latitude.toFixed(4)}, ${pos.longitude.toFixed(4)}`;
},
// 拖拽结束
(marker: Marker) => {
const pos = marker.getPosition();
console.info('[Marker] Drag end:', pos.latitude, pos.longitude);
this.dragInfo = `拖拽结束: ${pos.latitude.toFixed(4)}, ${pos.longitude.toFixed(4)}`;
}
));
// 3. InfoWindow点击事件
this.aMap.setOnInfoWindowClickListener((marker: Marker) => {
console.info('[Marker] InfoWindow clicked:', marker.getTitle());
marker.hideInfoWindow();
});
}
/**
* 添加默认样式标记
*/
private async addDefaultMarkers(): Promise<void> {
if (!this.aMap) return;
// 清除之前的标记
this.clearMarkers();
for (const place of this.places) {
const options = new MarkerOptions();
options.setPosition(new LatLng(place.lat, place.lng));
options.setTitle(place.name);
options.setSnippet(place.description);
options.setClickable(true);
options.setDraggable(false);
const marker = this.aMap.addMarker(options);
if (marker) {
this.markers.push(marker);
}
}
this.markerInfo = `已添加 ${this.markers.length} 个默认标记`;
}
/**
* 添加不同颜色的标记
* 注意:必须使用 defaultMarkerASync 异步方法创建彩色图标
*/
private async addColoredMarkers(): Promise<void> {
if (!this.aMap) return;
this.clearMarkers();
// 使用异步方法创建彩色标记图标
const colors: number[] = [
BitmapDescriptorFactory.HUE_GREEN,
BitmapDescriptorFactory.HUE_YELLOW,
BitmapDescriptorFactory.HUE_CYAN,
BitmapDescriptorFactory.HUE_VIOLET
];
const colorNames: string[] = ['绿色', '黄色', '青色', '紫色'];
for (let i = 0; i < this.places.length && i < colors.length; i++) {
const place = this.places[i];
const options = new MarkerOptions();
options.setPosition(new LatLng(place.lat, place.lng));
options.setTitle(`${place.name}(${colorNames[i]})`);
options.setClickable(true);
// 重要:使用 defaultMarkerASync 异步方法创建彩色图标
const icon = await BitmapDescriptorFactory.defaultMarkerASync(globalContext, colors[i]);
if (icon) {
options.setIcon(icon);
}
const marker = this.aMap.addMarker(options);
if (marker) {
this.markers.push(marker);
}
}
this.markerInfo = `已添加 ${this.markers.length} 个彩色标记`;
}
/**
* 添加自定义图标标记
*/
private async addCustomIconMarkers(): Promise<void> {
if (!this.aMap) return;
this.clearMarkers();
for (const place of this.places) {
const options = new MarkerOptions();
options.setPosition(new LatLng(place.lat, place.lng));
options.setTitle(place.name);
options.setSnippet(place.description);
// 从rawfile加载自定义图标
// 需要在entry/src/main/resources/rawfile/目录下放置图片
try {
const icon = await BitmapDescriptorFactory.fromRawfilePath(
globalContext,
'marker_custom.png'
);
if (icon) {
options.setIcon(icon);
}
} catch (e) {
console.warn('[Marker] Custom icon not found, using default');
}
// 设置锚点为底部中心
options.setAnchor(0.5, 1.0);
const marker = this.aMap.addMarker(options);
if (marker) {
this.markers.push(marker);
}
}
this.markerInfo = `已添加 ${this.markers.length} 个自定义图标标记`;
}
/**
* 添加可拖拽标记
*/
private async addDraggableMarkers(): Promise<void> {
if (!this.aMap) return;
this.clearMarkers();
for (const place of this.places) {
const options = new MarkerOptions();
options.setPosition(new LatLng(place.lat, place.lng));
options.setTitle(place.name);
options.setSnippet('可拖拽');
options.setDraggable(true); // 允许拖拽
options.setClickable(true);
// 使用橙色表示可拖拽
const icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE);
options.setIcon(icon);
const marker = this.aMap.addMarker(options);
if (marker) {
this.markers.push(marker);
}
}
this.markerInfo = `已添加 ${this.markers.length} 个可拖拽标记\n长按标记可拖动`;
this.dragInfo = '长按标记开始拖拽';
}
/**
* 添加自定义View标记
*/
private async addCustomViewMarkers(): Promise<void> {
if (!this.aMap) return;
this.clearMarkers();
for (let i = 0; i < this.places.length; i++) {
const place = this.places[i];
const options = new MarkerOptions();
options.setPosition(new LatLng(place.lat, place.lng));
options.setTitle(place.name);
// 使用Builder创建自定义View
const icon = await BitmapDescriptorFactory.fromView(() => {
this.CustomMarkerView(i + 1, place.name);
});
if (icon) {
options.setIcon(icon);
}
options.setAnchor(0.5, 1.0);
const marker = this.aMap.addMarker(options);
if (marker) {
this.markers.push(marker);
}
}
this.markerInfo = `已添加 ${this.markers.length} 个自定义View标记`;
}
/**
* 自定义标记View
*/
@Builder
CustomMarkerView(index: number, name: string) {
Column() {
// 编号气泡
Column() {
Text(index.toString())
.fontSize(14)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
}
.width(28)
.height(28)
.borderRadius(14)
.backgroundColor('#FF5722')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
// 名称标签
Text(name)
.fontSize(10)
.fontColor('#333')
.backgroundColor('rgba(255,255,255,0.9)')
.padding({ left: 4, right: 4, top: 2, bottom: 2 })
.borderRadius(4)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Center)
}
/**
* 清除所有标记
*/
private clearMarkers(): void {
if (!this.aMap) return;
// 方法1:逐个移除
// for (const marker of this.markers) {
// marker.remove();
// }
// 方法2:清除所有覆盖物
this.aMap.clear(true);
this.markers = [];
this.markerInfo = '已清除所有标记';
this.dragInfo = '';
}
aboutToAppear(): void {
globalContext = getContext(this).getApplicationContext();
MapViewManager.getInstance()
.registerMapViewCreatedCallback(this.mapViewCreateCallback);
}
aboutToDisappear(): void {
MapViewManager.getInstance()
.unregisterMapViewCreatedCallback(this.mapViewCreateCallback);
if (this.mapView) {
this.mapView.onDestroy();
this.mapView = undefined;
this.aMap = undefined;
}
}
build() {
Column() {
// 标题栏
Row() {
Text('地图标记 Marker')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
}
.width('100%')
.height(50)
.padding({ left: 16 })
.backgroundColor('#FF5722')
// 地图区域
Stack() {
MapViewComponent({ mapViewName: MAP_VIEW_NAME })
.width('100%')
.height('100%')
// 信息面板
Column() {
Text(this.markerInfo)
.fontSize(12)
.fontColor('#333')
if (this.dragInfo) {
Text(this.dragInfo)
.fontSize(11)
.fontColor('#666')
.margin({ top: 4 })
}
}
.padding(10)
.backgroundColor('rgba(255,255,255,0.95)')
.borderRadius(8)
.position({ x: 10, y: 10 })
}
.width('100%')
.layoutWeight(1)
// 操作按钮
Column() {
Row() {
Button('默认标记')
.fontSize(12)
.height(36)
.layoutWeight(1)
.margin({ right: 8 })
.onClick(() => this.addDefaultMarkers())
Button('彩色标记')
.fontSize(12)
.height(36)
.layoutWeight(1)
.margin({ right: 8 })
.onClick(() => this.addColoredMarkers())
Button('可拖拽')
.fontSize(12)
.height(36)
.layoutWeight(1)
.onClick(() => this.addDraggableMarkers())
}
.width('100%')
.margin({ bottom: 8 })
Row() {
Button('自定义View')
.fontSize(12)
.height(36)
.layoutWeight(1)
.margin({ right: 8 })
.onClick(() => this.addCustomViewMarkers())
Button('清除全部')
.fontSize(12)
.height(36)
.layoutWeight(1)
.backgroundColor('#F44336')
.onClick(() => this.clearMarkers())
}
.width('100%')
}
.padding(12)
.backgroundColor('#f5f5f5')
}
.width('100%')
.height('100%')
}
}
3. BitmapDescriptorFactory 方法
3.1 创建彩色图标(推荐使用异步方法)
// ⚠️ 重要:必须使用 defaultMarkerASync 异步方法创建彩色图标
// 同步方法 defaultMarker() 在鸿蒙SDK中可能无法正确显示颜色
// 需要先获取 context
let globalContext: Context;
// 在 aboutToAppear 中初始化
globalContext = getContext(this).getApplicationContext();
// 使用异步方法创建彩色图标
const icon = await BitmapDescriptorFactory.defaultMarkerASync(globalContext, BitmapDescriptorFactory.HUE_GREEN);
// 可用颜色常量
BitmapDescriptorFactory.HUE_RED // 红色 (0)
BitmapDescriptorFactory.HUE_ORANGE // 橙色 (30)
BitmapDescriptorFactory.HUE_YELLOW // 黄色 (60)
BitmapDescriptorFactory.HUE_GREEN // 绿色 (120)
BitmapDescriptorFactory.HUE_CYAN // 青色 (180)
BitmapDescriptorFactory.HUE_AZURE // 天蓝色 (210)
BitmapDescriptorFactory.HUE_BLUE // 蓝色 (240)
BitmapDescriptorFactory.HUE_VIOLET // 紫色 (270)
BitmapDescriptorFactory.HUE_MAGENTA // 洋红色 (300)
BitmapDescriptorFactory.HUE_ROSE // 玫红色 (330)
3.2 从rawfile加载图片
const icon = await BitmapDescriptorFactory.fromRawfilePath(
context,
'path/to/image.png'
);
3.3 从View创建
const icon = await BitmapDescriptorFactory.fromView(() => {
// 在这里构建UI组件
this.YourCustomBuilder();
});
4. Marker常用操作
4.1 更新标记属性
// 更新位置
marker.setPosition(new LatLng(39.9, 116.4));
// 更新标题
marker.setTitle('新标题');
// 更新图标
marker.setIcon(newIcon);
// 设置可见性
marker.setVisible(false);
// 设置旋转角度
marker.setRotateAngle(45);
4.2 InfoWindow操作
// 显示信息窗口
marker.showInfoWindow();
// 隐藏信息窗口
marker.hideInfoWindow();
// 检查是否显示
const isShowing = marker.isInfoWindowShown();
4.3 获取标记信息
const id = marker.getId();
const position = marker.getPosition();
const title = marker.getTitle();
const snippet = marker.getSnippet();
const isVisible = marker.isVisible();
const isDraggable = marker.isDraggable();
5. 锚点(Anchor)说明
锚点决定了图标哪个位置对应标记的地理坐标。
options.setAnchor(u, v);
// u: 水平方向 (0=左, 0.5=中, 1=右)
// v: 垂直方向 (0=上, 0.5=中, 1=下)
// 常用设置:
options.setAnchor(0.5, 1.0); // 底部中心(适合气泡样式)
options.setAnchor(0.5, 0.5); // 正中心(适合圆形图标)
6. 实用技巧
6.1 批量添加标记优化
// 添加多个标记时,建议关闭动画提升性能
for (const item of dataList) {
const options = new MarkerOptions();
options.setPosition(new LatLng(item.lat, item.lng));
// ... 其他设置
aMap.addMarker(options);
}
6.2 标记聚合
当标记数量较多时,可以考虑根据缩放级别显示/隐藏部分标记:
aMap.setOnCameraChangeListener(new OnCameraChangeListener(
() => {},
(position: CameraPosition) => {
const zoom = position.zoom;
// 根据缩放级别控制标记显示
this.markers.forEach((marker, index) => {
if (zoom < 10) {
// 缩放较小时只显示部分标记
marker.setVisible(index % 3 === 0);
} else {
marker.setVisible(true);
}
});
}
));
本篇小结
本篇教程我们学习了:
- ✅ 添加默认样式和彩色标记
- ✅ 使用自定义图标
- ✅ 创建自定义View标记
- ✅ 标记点击和拖拽事件处理
- ✅ InfoWindow的显示与隐藏
- ✅ 锚点设置与标记属性更新
下一篇我们将学习定位蓝点功能。 班级 developer.huawei.com/consumer/cn…