自定义地图标记与气泡弹窗

鸿蒙原生开发手记:徒步迹 - 自定义地图标记与气泡弹窗

打造个性化地图交互,提升用户体验


一、前言

默认的地图标记样式单一,不能满足 App 的品牌需求。本文介绍如何实现自定义地图标记图标和气泡弹窗(InfoWindow),让地图交互更丰富。


二、自定义标记图标

2.1 使用自定义图片

function addCustomMarker(map: MapView): void {
  map.addMarker({
    position: { lat: 39.908, lng: 116.397 },
    title: '徒步起点',
    icon: 'custom_marker_start',    // resources/base/media 中的图片
    anchorPoint: { x: 0.5, y: 1 }, // 锚点(底部居中)
    zIndex: 10,                     // 层级
    alpha: 1.0,                     // 透明度
    draggable: false,               // 是否可拖拽
  });
}

2.2 不同状态的标记样式

enum MarkerType {
  START,       // 起点(绿色)
  WAYPOINT,    // 途经点(蓝色)
  END,         // 终点(红色)
  CURRENT,     // 当前位置(脉冲动画)
}

function getMarkerIcon(type: MarkerType): string {
  switch (type) {
    case MarkerType.START:
      return 'marker_start';
    case MarkerType.WAYPOINT:
      return 'marker_waypoint';
    case MarkerType.END:
      return 'marker_end';
    case MarkerType.CURRENT:
      return 'marker_current';
  }
}

// 批量添加路线标记
function addRouteMarkers(map: MapView, waypoints: Waypoint[]): void {
  waypoints.forEach((wp, index) => {
    let type: MarkerType;
    if (index === 0) type = MarkerType.START;
    else if (index === waypoints.length - 1) type = MarkerType.END;
    else type = MarkerType.WAYPOINT;

    map.addMarker({
      position: { lat: wp.latitude, lng: wp.longitude },
      title: wp.name,
      icon: getMarkerIcon(type),
    });
  });
}

三、气泡弹窗(InfoWindow)

3.1 基础弹窗

function showInfoWindow(map: MapView, marker: Marker): void {
  marker.showInfoWindow({
    title: '香山徒步起点',
    snippet: '海拔 120m · 距市区 20km',
    // 点击弹窗事件
    onClick: () => {
      console.log('弹窗被点击');
      // 跳转到路线详情页
      router.pushUrl({ url: 'pages/RouteDetailPage' });
    },
  });
}

3.2 自定义信息窗

// 使用自定义组件作为信息窗
@Component
struct CustomInfoWindow {
  @Prop routeName: string = '';
  @Prop distance: string = '';
  @Prop elevation: string = '';
  @Prop difficulty: string = '';

  build() {
    Column() {
      Text(this.routeName)
        .fontSize(14)
        .fontWeight(FontWeight.Bold);

      Row() {
        Text(`距离: ${this.distance}`).fontSize(12).fontColor('#666');
        Text(`爬升: ${this.elevation}`).fontSize(12).fontColor('#666').margin({ left: 8 });
      }
      .margin({ top: 4 });

      Text(`难度: ${this.difficulty}`)
        .fontSize(12)
        .fontColor('#4CAF50')
        .margin({ top: 2 });
    }
    .padding(10)
    .backgroundColor(Color.White)
    .borderRadius(8);
  }
}

// 在标记点击时显示自定义弹窗
map.onMarkerClick((marker: Marker) => {
  // 使用自定义布局
  marker.showInfoWindow({
    customView: {
      component: CustomInfoWindow({
        routeName: '香山徒步',
        distance: '8.5km',
        elevation: '320m',
        difficulty: '中等',
      }),
      width: 200,
      height: 120,
    },
  });
});

四、标记聚合

当地图上有大量标记时,可以使用标记聚合功能:

function enableMarkerClustering(map: MapView): void {
  // 启用聚合
  map.setMarkerClustering(true);

  // 设置聚合样式
  map.setClusterOptions({
    icon: 'cluster_icon',
    textColor: Color.White,
    textSize: 14,
    clusterRange: 60,   // 聚合距离(px)
  });
}

五、动画与交互

5.1 标记动画

// 标记点击时的弹跳动画
function animateMarkerBounce(marker: Marker): void {
  animateTo({ duration: 300, curve: Curve.SpringMotion }, () => {
    marker.setAlpha(0.5);   // 闪烁
  });

  setTimeout(() => {
    animateTo({ duration: 300 }, () => {
      marker.setAlpha(1.0);
    });
  }, 300);
}

5.2 地图区域搜索

async function searchNearbyRoutes(
  map: MapView,
  center: { lat: number; lng: number },
  radius: number
): Promise<void> {
  // 显示搜索范围圈
  map.addCircle({
    center: center,
    radius: radius,
    fillColor: 'rgba(76, 175, 80, 0.1)',
    strokeColor: '#4CAF50',
    strokeWidth: 2,
  });

  // 搜索附近的路线并添加标记
  const routes = await apiService.getRoutesByLocation(center.lat, center.lng, radius);
  for (let route of routes) {
    map.addMarker({
      position: { lat: route.latitude, lng: route.longitude },
      title: route.name,
      icon: 'route_marker',
    });
  }
}

六、实战:徒步迹路线详情地图

@Component
struct RouteMapView {
  @Prop routeId: number = 0;
  private mapView!: MapView;

  aboutToAppear(): void {
    // 页面加载后初始化地图
  }

  build() {
    Column() {
      MapComponent({
        mapOptions: {
          center: { lat: 39.908, lng: 116.397 },
          zoom: 13,
        },
        onMapReady: (map: MapView) => {
          this.mapView = map;
          this.loadRouteData();
        },
      })
      .width('100%')
      .height(300);

      Row() {
        Text('香山徒步').fontSize(16).fontWeight(FontWeight.Bold);
        Text('8.5km · 中等').fontSize(13).fontColor('#666').margin({ left: 8 });
      }
      .width('100%')
      .padding(12);
    }
  }

  loadRouteData(): void {
    // 绘制路线
    const points = [
      { lat: 39.908, lng: 116.397 },
      { lat: 39.912, lng: 116.400 },
      { lat: 39.915, lng: 116.405 },
      { lat: 39.918, lng: 116.402 },
    ];

    this.mapView.addPolyline({
      path: points,
      strokeWidth: 5,
      strokeColor: '#4CAF50',
    });

    // 添加起终点标记
    this.mapView.addMarker({
      position: points[0],
      title: '起点', icon: 'marker_start',
    });
    this.mapView.addMarker({
      position: points[points.length - 1],
      title: '终点', icon: 'marker_end',
    });
  }
}

七、总结

自定义标记和弹窗是提升地图交互体验的重要手段。通过自定义图标、气泡弹窗和标记聚合,可以让“徒步迹“的地图展示更加个性化和实用。

下一篇文章将开发天气卡片组件。


下一篇预告:鸿蒙原生开发手记:徒步迹 - 天气卡片组件开发

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐