第十篇:综合实战案例

本篇教程将整合前面所学的所有功能,实现一个完整的「周边生活服务」应用。

学习目标

  • 整合地图显示、定位、搜索、路线规划等功能
  • 实现完整的业务流程
  • 学习最佳实践和代码组织方式

1. 应用功能概述

我们将实现一个「周边生活服务」应用,包含以下功能:

  • 📍 显示当前位置
  • 🔍 搜索周边商家(餐厅、超市、银行等)
  • 📋 展示搜索结果列表
  • 🗺️ 在地图上标记搜索结果
  • 🚗 规划到选中商家的路线
  • 📝 显示商家详细信息

2. 完整代码实现

创建文件 entry/src/main/ets/pages/Demo09_CompleteApp.ets

import {
  AMap,
  MapView,
  MapViewComponent,
  MapViewManager,
  MapViewCreateCallback,
  CameraUpdateFactory,
  LatLng,
  LatLngBounds,
  Marker,
  MarkerOptions,
  Polyline,
  PolylineOptions,
  BitmapDescriptorFactory,
  MyLocationStyle,
  OnLocationChangedListener
} from '@amap/amap_lbs_map3d';
import {
  PoiSearch,
  PoiQuery,
  PoiResult,
  PoiItem,
  OnPoiSearchListener,
  PoiSearchBound,
  RouteSearch,
  DriveRouteQuery,
  WalkRouteQuery,
  DriveRouteResult,
  WalkRouteResult,
  BusRouteResult,
  RideRouteResult,
  FromAndTo,
  LatLonPoint,
  OnRouteSearchListener,
  AMapException,
  DrivePath,
  WalkPath,
  DriveStep,
  WalkStep,
  GeocodeSearch,
  ReGeocodeQuery,
  ReGeocodeAddress
} from '@amap/amap_lbs_search';
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { geoLocationManager } from '@kit.LocationKit';
import { promptAction } from '@kit.ArkUI';

const MAP_VIEW_NAME = 'CompleteApp';

/**
 * 商家分类
 */
interface Category {
  name: string;
  icon: string;
  keyword: string;
}

/**
 * 路线类型
 */
type RouteMode = 'drive' | 'walk';

@Entry
@Component
struct Demo09_CompleteApp {
  private mapView: MapView | undefined = undefined;
  private aMap: AMap | undefined = undefined;
  private poiSearch: PoiSearch | undefined = undefined;
  private routeSearch: RouteSearch | undefined = undefined;
  private geocodeSearch: GeocodeSearch | undefined = undefined;
  
  // 覆盖物
  private poiMarkers: Marker[] = [];
  private myLocationMarker: Marker | undefined = undefined;
  private routePolyline: Polyline | undefined = undefined;
  
  // 状态
  @State isMapReady: boolean = false;
  @State hasPermission: boolean = false;
  @State isSearching: boolean = false;
  @State isRouting: boolean = false;
  
  // 位置
  @State currentLocation: LatLng | undefined = undefined;
  @State currentAddress: string = '定位中...';
  
  // 搜索
  @State selectedCategory: string = '餐厅';
  @State searchRadius: number = 2000;
  @State poiList: PoiItem[] = [];
  @State selectedPoi: PoiItem | undefined = undefined;
  
  // 路线
  @State routeMode: RouteMode = 'drive';
  @State routeInfo: string = '';
  @State showRoutePanel: boolean = false;
  
  // 商家分类
  private categories: Category[] = [
    { name: '餐厅', icon: '🍜', keyword: '餐厅' },
    { name: '超市', icon: '🛒', keyword: '超市' },
    { name: '银行', icon: '🏦', keyword: '银行' },
    { name: '药店', icon: '💊', keyword: '药店' },
    { name: '加油站', icon: '⛽', keyword: '加油站' },
    { name: '停车场', icon: '🅿️', keyword: '停车场' }
  ];

  /**
   * POI搜索回调
   */
  private poiSearchListener: OnPoiSearchListener = {
    onPoiSearched: (result: PoiResult | undefined, errorCode: number) => {
      this.isSearching = false;
      
      if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
        const pois = result.getPois();
        if (pois && pois.length > 0) {
          this.poiList = pois as PoiItem[];
          this.addPoiMarkersToMap();
          promptAction.showToast({ message: `找到 ${pois.length}${this.selectedCategory}` });
        } else {
          this.poiList = [];
          promptAction.showToast({ message: '附近没有找到相关商家' });
        }
      } else {
        promptAction.showToast({ message: '搜索失败,请重试' });
      }
    },
    onPoiItemSearched: () => {}
  };

  /**
   * 路线搜索回调
   */
  private routeSearchListener: OnRouteSearchListener = {
    onDriveRouteSearched: (result: DriveRouteResult | undefined, errorCode: number) => {
      this.isRouting = false;
      
      if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
        const paths = result.getPaths();
        if (paths && paths.length > 0) {
          const path = paths[0] as DrivePath;
          this.showRoute(path.getSteps(), path.getDistance(), path.getDuration(), '驾车');
        }
      } else {
        this.routeInfo = '路线规划失败';
      }
    },
    onWalkRouteSearched: (result: WalkRouteResult | undefined, errorCode: number) => {
      this.isRouting = false;
      
      if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
        const paths = result.getPaths();
        if (paths && paths.length > 0) {
          const path = paths[0] as WalkPath;
          this.showRoute(path.getSteps(), path.getDistance(), path.getDuration(), '步行');
        }
      } else {
        this.routeInfo = '路线规划失败';
      }
    },
    onBusRouteSearched: (result: BusRouteResult | undefined, errorCode: number) => {},
    onRideRouteSearched: (result: RideRouteResult | undefined, errorCode: number) => {}
  };

  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 defaultLocation = new LatLng(39.909187, 116.397451);
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultLocation, 15));
        
        // 启用控件
        map.getUiSettings()?.setZoomControlsEnabled(true);
        map.getUiSettings()?.setScaleControlsEnabled(true);
        
        // 请求定位权限
        this.requestLocationPermission();
        
        // 地图点击关闭详情面板
        map.setOnMapClickListener(() => {
          this.selectedPoi = undefined;
          this.showRoutePanel = false;
        });
      });
    };

  /**
   * 请求定位权限
   */
  private async requestLocationPermission(): Promise<void> {
    try {
      const permissions: Permissions[] = [
        'ohos.permission.APPROXIMATELY_LOCATION',
        'ohos.permission.LOCATION'
      ];
      
      const context = getContext(this) as common.UIAbilityContext;
      const atManager = abilityAccessCtrl.createAtManager();
      const result = await atManager.requestPermissionsFromUser(context, permissions);
      
      if (result.authResults.every(status => status === 0)) {
        this.hasPermission = true;
        this.startLocation();
      } else {
        this.currentAddress = '未授权定位';
        // 使用默认位置
        this.currentLocation = new LatLng(39.909187, 116.397451);
      }
    } catch (error) {
      console.error('[App] Permission request failed:', error);
    }
  }

  /**
   * 开始定位
   */
  private startLocation(): void {
    const request: geoLocationManager.CurrentLocationRequest = {
      priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
      scenario: geoLocationManager.LocationRequestScenario.UNSET
    };
    
    geoLocationManager.getCurrentLocation(request)
      .then((location) => {
        this.currentLocation = new LatLng(location.latitude, location.longitude);
        // 使用逆地理编码获取城市名称
        this.getAddressFromLocation(location.latitude, location.longitude);
        
        // 移动地图到当前位置
        if (this.aMap) {
          this.aMap.animateCamera(
            CameraUpdateFactory.newLatLngZoom(this.currentLocation, 15),
            500
          );
        }
        
        // 添加定位标记
        this.addMyLocationMarker();
      })
      .catch((error) => {
        console.error('[App] Location failed:', error);
        this.currentAddress = '定位失败';
        // 使用默认位置
        this.currentLocation = new LatLng(39.909187, 116.397451);
        this.getAddressFromLocation(39.909187, 116.397451);
      });
  }

  /**
   * 逆地理编码获取地址
   * 将坐标转换为城市、区域、街道等地址信息
   */
  private async getAddressFromLocation(lat: number, lng: number): Promise<void> {
    if (!this.geocodeSearch) return;
    
    try {
      const point: LatLonPoint = new LatLonPoint(lat, lng);
      const query: ReGeocodeQuery = new ReGeocodeQuery(point, 200, GeocodeSearch.AMAP);
      // getFromLocation 直接返回 ReGeocodeAddress
      const address: ReGeocodeAddress | undefined = await this.geocodeSearch.getFromLocation(query);
      
      if (address) {
        // 获取城市和区域信息
        const city: string = address.getCity() || '';
        const district: string = address.getDistrict() || '';
        const township: string = address.getTownship() || '';
        
        if (city) {
          this.currentAddress = `${city} ${district} ${township}`.trim();
        } else {
          // 如果没有城市信息,使用格式化地址
          this.currentAddress = address.getFormatAddress() || '未知位置';
        }
      }
    } catch (error) {
      this.currentAddress = '获取地址失败';
    }
  }

  /**
   * 添加定位标记
   */
  private addMyLocationMarker(): void {
    if (!this.aMap || !this.currentLocation) return;
    
    if (this.myLocationMarker) {
      this.myLocationMarker.remove();
    }
    
    const options = new MarkerOptions();
    options.setPosition(this.currentLocation);
    options.setTitle('我的位置');
    options.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
    options.setAnchor(0.5, 1.0);
    options.setZIndex(100);
    
    this.myLocationMarker = this.aMap.addMarker(options);
  }

  /**
   * 搜索周边商家
   * 使用 PoiSearchBound.createCircleSearchBound 创建圆形搜索范围
   */
  private searchNearby(category: Category): void {
    if (!this.poiSearch || !this.currentLocation) {
      promptAction.showToast({ message: '请先获取位置' });
      return;
    }
    
    this.selectedCategory = category.name;
    this.isSearching = true;
    this.selectedPoi = undefined;
    this.showRoutePanel = false;
    this.clearRoute();
    
    // 创建当前位置的坐标点
    const centerPoint: LatLonPoint = new LatLonPoint(
      this.currentLocation.latitude,
      this.currentLocation.longitude
    );
    
    // 使用当前位置进行周边搜索,不限定城市
    const query: PoiQuery = new PoiQuery(category.keyword, '', '');
    query.setPageSize(20);
    query.setPageNum(0);
    
    // 创建圆形搜索范围:以当前位置为中心,半径3000米
    const searchBound: PoiSearchBound = PoiSearchBound.createCircleSearchBound(centerPoint, 3000);
    
    this.poiSearch.setQuery(query);
    this.poiSearch.setBound(searchBound);
    this.poiSearch.searchPOIAsyn();
  }

  /**
   * 添加POI标记到地图
   */
  private addPoiMarkersToMap(): void {
    if (!this.aMap) return;
    
    // 清除旧标记
    this.clearPoiMarkers();
    
    for (let i = 0; i < this.poiList.length; i++) {
      const poi = this.poiList[i];
      const point = poi.getLatLonPoint();
      
      if (point) {
        const options = new MarkerOptions();
        options.setPosition(new LatLng(point.getLatitude(), point.getLongitude()));
        options.setTitle(poi.getTitle() || '');
        options.setSnippet(poi.getSnippet() || '');
        
        // 前3个用红色,其他用蓝色
        const hue = i < 3 ? BitmapDescriptorFactory.HUE_RED : BitmapDescriptorFactory.HUE_BLUE;
        options.setIcon(BitmapDescriptorFactory.defaultMarker(hue));
        options.setAnchor(0.5, 1.0);
        options.setZIndex(10);
        
        const marker = this.aMap.addMarker(options);
        if (marker) {
          this.poiMarkers.push(marker);
        }
      }
    }
    
    // 设置标记点击事件
    this.aMap.setOnMarkerClickListener((marker: Marker): boolean => {
      const position = marker.getPosition();
      
      // 查找对应的POI
      for (const poi of this.poiList) {
        const point = poi.getLatLonPoint();
        if (point && 
            Math.abs(position.latitude - point.getLatitude()) < 0.0001 &&
            Math.abs(position.longitude - point.getLongitude()) < 0.0001) {
          this.selectPoi(poi);
          break;
        }
      }
      
      return true;
    });
  }

  /**
   * 清除POI标记
   */
  private clearPoiMarkers(): void {
    for (const marker of this.poiMarkers) {
      marker.remove();
    }
    this.poiMarkers = [];
  }

  /**
   * 选择POI
   */
  private selectPoi(poi: PoiItem): void {
    this.selectedPoi = poi;
    this.showRoutePanel = false;
    this.clearRoute();
    
    // 移动地图到POI位置
    const point = poi.getLatLonPoint();
    if (point && this.aMap) {
      this.aMap.animateCamera(
        CameraUpdateFactory.newLatLng(new LatLng(point.getLatitude(), point.getLongitude())),
        300
      );
    }
  }

  /**
   * 规划路线到选中的POI
   */
  private planRoute(): void {
    if (!this.routeSearch || !this.currentLocation || !this.selectedPoi) return;
    
    const poiPoint = this.selectedPoi.getLatLonPoint();
    if (!poiPoint) return;
    
    this.isRouting = true;
    this.showRoutePanel = true;
    this.routeInfo = '正在规划路线...';
    
    const fromAndTo = new FromAndTo(
      new LatLonPoint(this.currentLocation.latitude, this.currentLocation.longitude),
      poiPoint
    );
    
    if (this.routeMode === 'drive') {
      const query = new DriveRouteQuery(fromAndTo, RouteSearch.DrivingDefault, undefined, undefined, '');
      this.routeSearch.calculateDriveRouteAsyn(query);
    } else {
      const query = new WalkRouteQuery(fromAndTo);
      this.routeSearch.calculateWalkRouteAsyn(query);
    }
  }

  /**
   * 显示路线
   */
  private showRoute(steps: Object[] | undefined, distance: number, duration: number, mode: string): void {
    if (!this.aMap || !steps) return;
    
    this.clearRoute();
    
    // 收集坐标点
    const points: LatLng[] = [];
    for (const step of steps) {
      const polyline = (step as DriveStep).getPolyline?.() || (step as WalkStep).getPolyline?.();
      if (polyline) {
        for (const point of polyline) {
          points.push(new LatLng(point.getLatitude(), point.getLongitude()));
        }
      }
    }
    
    if (points.length > 0) {
      // 绘制路线
      const options = new PolylineOptions();
      options.setPoints(points);
      options.setWidth(10);
      options.setColor(this.routeMode === 'drive' ? 0xFF4CAF50 : 0xFF2196F3);
      
      this.routePolyline = this.aMap.addPolyline(options);
      
      // 更新路线信息
      this.routeInfo = `${mode}路线\n距离: ${this.formatDistance(distance)}\n预计: ${this.formatDuration(duration)}`;
      
      // 调整视野
      this.fitRouteBounds(points);
    }
  }

  /**
   * 清除路线
   */
  private clearRoute(): void {
    if (this.routePolyline) {
      this.routePolyline.remove();
      this.routePolyline = undefined;
    }
    this.routeInfo = '';
  }

  /**
   * 调整视野以显示路线
   */
  private fitRouteBounds(points: LatLng[]): void {
    if (!this.aMap || points.length === 0) return;
    
    let minLat = 90, maxLat = -90, minLng = 180, maxLng = -180;
    for (const point of points) {
      minLat = Math.min(minLat, point.latitude);
      maxLat = Math.max(maxLat, point.latitude);
      minLng = Math.min(minLng, point.longitude);
      maxLng = Math.max(maxLng, point.longitude);
    }
    
    const bounds = new LatLngBounds(
      new LatLng(minLat, minLng),
      new LatLng(maxLat, maxLng)
    );
    
    this.aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 80), 500);
  }

  /**
   * 格式化距离
   */
  private formatDistance(meters: number): string {
    return meters < 1000 ? `${meters}` : `${(meters / 1000).toFixed(1)}公里`;
  }

  /**
   * 格式化时间
   */
  private formatDuration(seconds: number): string {
    const minutes = Math.floor(seconds / 60);
    return minutes < 60 ? `${minutes}分钟` : `${Math.floor(minutes / 60)}小时${minutes % 60}分钟`;
  }

  aboutToAppear(): void {
    MapViewManager.getInstance()
      .registerMapViewCreatedCallback(this.mapViewCreateCallback);
    
    const context = getContext(this);
    this.poiSearch = new PoiSearch(context, undefined);
    this.poiSearch.setOnPoiSearchListener(this.poiSearchListener);
    
    this.routeSearch = new RouteSearch(context);
    this.routeSearch.setRouteSearchListener(this.routeSearchListener);
    
    // 初始化逆地理编码
    this.geocodeSearch = new GeocodeSearch(context);
  }

  aboutToDisappear(): void {
    this.clearPoiMarkers();
    this.clearRoute();
    
    MapViewManager.getInstance()
      .unregisterMapViewCreatedCallback(this.mapViewCreateCallback);
    
    if (this.mapView) {
      this.mapView.onDestroy();
      this.mapView = undefined;
      this.aMap = undefined;
    }
  }

  build() {
    Stack() {
      Column() {
        // 顶部搜索栏
        Column() {
          // 标题和定位信息
          Row() {
            Text('📍')
              .fontSize(18)
            Text(this.currentAddress)
              .fontSize(14)
              .fontColor('#333')
              .layoutWeight(1)
              .margin({ left: 8 })
              .maxLines(1)
            
            Button('重新定位')
              .fontSize(12)
              .height(30)
              .backgroundColor('#2196F3')
              .onClick(() => this.startLocation())
          }
          .width('100%')
          .padding({ bottom: 10 })

          // 分类按钮
          Scroll() {
            Row() {
              ForEach(this.categories, (cat: Category) => {
                Column() {
                  Text(cat.icon)
                    .fontSize(24)
                  Text(cat.name)
                    .fontSize(11)
                    .fontColor(this.selectedCategory === cat.name ? '#2196F3' : '#666')
                    .margin({ top: 4 })
                }
                .width(60)
                .height(60)
                .backgroundColor(this.selectedCategory === cat.name ? '#E3F2FD' : '#f5f5f5')
                .borderRadius(8)
                .justifyContent(FlexAlign.Center)
                .margin({ right: 8 })
                .onClick(() => this.searchNearby(cat))
              })
            }
          }
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)
        }
        .width('100%')
        .padding(12)
        .backgroundColor(Color.White)

        // 地图
        Stack() {
          MapViewComponent({ mapViewName: MAP_VIEW_NAME })
            .width('100%')
            .height('100%')
          
          // 加载指示器
          if (this.isSearching) {
            Column() {
              LoadingProgress()
                .width(40)
                .height(40)
              Text('搜索中...')
                .fontSize(12)
                .margin({ top: 8 })
            }
            .width('100%')
            .height('100%')
            .backgroundColor('rgba(255,255,255,0.7)')
            .justifyContent(FlexAlign.Center)
          }
        }
        .layoutWeight(1)

        // 底部结果列表
        if (this.poiList.length > 0 && !this.selectedPoi) {
          Column() {
            Text(`${this.selectedCategory} (${this.poiList.length}个结果)`)
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .padding(10)
              .width('100%')
              .backgroundColor('#f5f5f5')
            
            List() {
              ForEach(this.poiList, (poi: PoiItem, index: number) => {
                ListItem() {
                  Row() {
                    Text(`${index + 1}`)
                      .fontSize(12)
                      .fontColor(Color.White)
                      .width(24)
                      .height(24)
                      .textAlign(TextAlign.Center)
                      .backgroundColor(index < 3 ? '#F44336' : '#2196F3')
                      .borderRadius(12)
                    
                    Column() {
                      Text(poi.getTitle() || '未知商家')
                        .fontSize(14)
                        .fontColor('#333')
                        .maxLines(1)
                      Row() {
                        Text(poi.getSnippet() || '')
                          .fontSize(11)
                          .fontColor('#999')
                          .maxLines(1)
                          .layoutWeight(1)
                        if (poi.getDistance() > 0) {
                          Text(this.formatDistance(poi.getDistance()))
                            .fontSize(11)
                            .fontColor('#2196F3')
                        }
                      }
                      .width('100%')
                      .margin({ top: 2 })
                    }
                    .layoutWeight(1)
                    .alignItems(HorizontalAlign.Start)
                    .margin({ left: 10 })
                  }
                  .width('100%')
                  .padding(12)
                }
                .onClick(() => this.selectPoi(poi))
              })
            }
            .height(150)
            .divider({ strokeWidth: 1, color: '#eee' })
          }
          .backgroundColor(Color.White)
        }
      }
      .width('100%')
      .height('100%')

      // 商家详情面板
      if (this.selectedPoi) {
        Column() {
          // 商家信息
          Column() {
            Row() {
              Text(this.selectedPoi.getTitle() || '未知商家')
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .layoutWeight(1)
              
              Text('✕')
                .fontSize(20)
                .fontColor('#999')
                .onClick(() => {
                  this.selectedPoi = undefined;
                  this.showRoutePanel = false;
                  this.clearRoute();
                })
            }
            .width('100%')
            
            Text(this.selectedPoi.getSnippet() || '')
              .fontSize(13)
              .fontColor('#666')
              .margin({ top: 8 })
              .width('100%')
            
            if (this.selectedPoi.getTel()) {
              Text(`📞 ${this.selectedPoi.getTel()}`)
                .fontSize(13)
                .fontColor('#2196F3')
                .margin({ top: 4 })
            }
            
            if (this.selectedPoi.getDistance() > 0) {
              Text(`📍 距您 ${this.formatDistance(this.selectedPoi.getDistance())}`)
                .fontSize(13)
                .fontColor('#666')
                .margin({ top: 4 })
            }
          }
          .padding(16)
          .width('100%')
          .alignItems(HorizontalAlign.Start)

          // 路线规划
          Column() {
            Row() {
              Button('🚗 驾车')
                .fontSize(13)
                .height(36)
                .layoutWeight(1)
                .backgroundColor(this.routeMode === 'drive' ? '#4CAF50' : '#e0e0e0')
                .fontColor(this.routeMode === 'drive' ? Color.White : '#333')
                .margin({ right: 8 })
                .onClick(() => { this.routeMode = 'drive'; })
              
              Button('🚶 步行')
                .fontSize(13)
                .height(36)
                .layoutWeight(1)
                .backgroundColor(this.routeMode === 'walk' ? '#2196F3' : '#e0e0e0')
                .fontColor(this.routeMode === 'walk' ? Color.White : '#333')
                .onClick(() => { this.routeMode = 'walk'; })
            }
            .width('100%')
            
            Button(this.isRouting ? '规划中...' : '开始导航')
              .width('100%')
              .height(44)
              .margin({ top: 12 })
              .backgroundColor('#FF5722')
              .enabled(!this.isRouting)
              .onClick(() => this.planRoute())
            
            // 路线信息
            if (this.showRoutePanel && this.routeInfo) {
              Text(this.routeInfo)
                .fontSize(13)
                .fontColor('#333')
                .margin({ top: 12 })
                .padding(10)
                .width('100%')
                .backgroundColor('#f5f5f5')
                .borderRadius(8)
            }
          }
          .padding(16)
          .width('100%')
          .backgroundColor('#fafafa')
        }
        .width('100%')
        .backgroundColor(Color.White)
        .borderRadius({ topLeft: 16, topRight: 16 })
        .position({ bottom: 0 })
        .shadow({ radius: 10, color: 'rgba(0,0,0,0.1)' })
      }
    }
    .width('100%')
    .height('100%')
  }
}

3. 配置路由页面

更新 entry/src/main/resources/base/profile/main_pages.json

{
  "src": [
    "pages/Index",
    "pages/Demo01_BasicMap",
    "pages/Demo02_MapControl",
    "pages/Demo03_Marker",
    "pages/Demo04_Location",
    "pages/Demo05_PoiSearch",
    "pages/Demo06_Geocode",
    "pages/Demo07_Route",
    "pages/Demo08_Drawing",
    "pages/Demo09_CompleteApp"
  ]
}

4. 创建教程首页

更新 entry/src/main/ets/pages/Index.ets

import { router } from '@kit.ArkUI';

interface DemoItem {
  title: string;
  subtitle: string;
  page: string;
  color: string;
}

@Entry
@Component
struct Index {
  private demos: DemoItem[] = [
    { title: '显示地图', subtitle: '第一个地图应用', page: 'pages/Demo01_BasicMap', color: '#4CAF50' },
    { title: '地图控制', subtitle: '类型切换与UI设置', page: 'pages/Demo02_MapControl', color: '#2196F3' },
    { title: '地图标记', subtitle: 'Marker功能演示', page: 'pages/Demo03_Marker', color: '#FF5722' },
    { title: '定位功能', subtitle: '定位蓝点显示', page: 'pages/Demo04_Location', color: '#03A9F4' },
    { title: 'POI搜索', subtitle: '周边商家搜索', page: 'pages/Demo05_PoiSearch', color: '#9C27B0' },
    { title: '地理编码', subtitle: '地址与坐标转换', page: 'pages/Demo06_Geocode', color: '#009688' },
    { title: '路线规划', subtitle: '驾车/步行路线', page: 'pages/Demo07_Route', color: '#673AB7' },
    { title: '绑图测距', subtitle: '绘制与测量', page: 'pages/Demo08_Drawing', color: '#E91E63' },
    { title: '综合案例', subtitle: '周边生活服务', page: 'pages/Demo09_CompleteApp', color: '#FF9800' }
  ];

  build() {
    Column() {
      // 标题
      Text('高德地图开发教程')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 40, bottom: 20 })
      
      Text('HarmonyOS 实战案例')
        .fontSize(14)
        .fontColor('#666')
        .margin({ bottom: 30 })

      // 案例列表
      List() {
        ForEach(this.demos, (demo: DemoItem, index: number) => {
          ListItem() {
            Row() {
              Column() {
                Text(`${index + 1}`)
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(Color.White)
              }
              .width(40)
              .height(40)
              .backgroundColor(demo.color)
              .borderRadius(20)
              .justifyContent(FlexAlign.Center)
              
              Column() {
                Text(demo.title)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)
                  .fontColor('#333')
                Text(demo.subtitle)
                  .fontSize(12)
                  .fontColor('#999')
                  .margin({ top: 4 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 16 })
              
              Text('>')
                .fontSize(18)
                .fontColor('#ccc')
            }
            .width('100%')
            .padding(16)
            .backgroundColor(Color.White)
            .borderRadius(12)
            .shadow({ radius: 4, color: 'rgba(0,0,0,0.05)' })
          }
          .margin({ bottom: 12 })
          .onClick(() => {
            router.pushUrl({ url: demo.page });
          })
        })
      }
      .layoutWeight(1)
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

5. 最佳实践总结

5.1 代码组织

  • 将地图相关逻辑封装成独立的Service
  • 使用常量管理API Key和配置
  • 合理划分页面和组件

5.2 性能优化

  • 及时清理不需要的覆盖物
  • 控制标记数量,避免过多标记影响性能
  • 使用异步搜索方法

5.3 用户体验

  • 添加加载状态提示
  • 合理的错误处理和提示
  • 平滑的地图动画

6. 系列教程回顾

通过本系列10篇教程,我们系统学习了:

  1. SDK集成 - 安装配置、隐私政策
  2. 地图显示 - MapViewComponent、生命周期
  3. 地图控制 - 类型切换、UI控件、事件监听
  4. 地图标记 - Marker添加、自定义样式
  5. 定位功能 - 权限申请、定位蓝点
  6. POI搜索 - 关键字搜索、周边搜索
  7. 地理编码 - 地址坐标互转
  8. 路线规划 - 驾车、步行、骑行
  9. 绘图测距 - 折线、多边形、面积计算
  10. 综合案例 - 完整应用实现

恭喜完成! 🎉

你已经掌握了在鸿蒙应用中使用高德地图SDK的核心技能。现在可以开始构建你自己的地图应用了!

班级
https://developer.huawei.com/consumer/cn/training/classDetail/fd34ff9286174e848d34cde7f512ce22?type=1%3Fha_source%3Dhmosclass&ha_sourceId=89000248

源码地址
https://gitcode.com/daleishen/gaodehmjiaocheng.git

Logo

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

更多推荐