HarmonyOS中开发高德地图第五篇:定位蓝点功能

第五篇:定位蓝点功能

本篇教程将学习如何在地图上显示用户当前位置(定位蓝点),包括权限申请、定位样式自定义等。

学习目标

  • 申请定位权限
  • 显示定位蓝点
  • 自定义定位蓝点样式
  • 理解不同的定位模式

1. 定位权限配置

1.1 module.json5 权限声明

json5 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:permission_location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:permission_location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

1.2 权限说明

权限 说明
ohos.permission.LOCATION 精确定位权限
ohos.permission.APPROXIMATELY_LOCATION 模糊定位权限

注意: 申请精确定位时必须同时申请模糊定位权限。

2. 定位模式说明

MyLocationStyle 提供了多种定位模式:

模式 常量 说明
只展示 LOCATION_TYPE_SHOW 只显示位置,不移动地图
定位一次 LOCATION_TYPE_LOCATE 定位一次并移动到该位置
跟随 LOCATION_TYPE_FOLLOW 持续跟随位置移动
地图旋转 LOCATION_TYPE_MAP_ROTATE 地图随手机方向旋转
定位点旋转 LOCATION_TYPE_LOCATION_ROTATE 定位点随方向旋转
跟随不移中心 LOCATION_TYPE_FOLLOW_NO_CENTER 跟随但不移动地图中心
地图旋转不移中心 LOCATION_TYPE_MAP_ROTATE_NO_CENTER 地图旋转但不移中心
定位点旋转不移中心 LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER 定位点旋转但不移中心

3. 完整代码示例

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

typescript 复制代码
import {
  AMap,
  MapView,
  MapViewComponent,
  MapViewManager,
  MapViewCreateCallback,
  CameraUpdateFactory,
  LatLng,
  MyLocationStyle,
  BitmapDescriptorFactory,
  OnLocationChangedListener
} from '@amap/amap_lbs_map3d';
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { geoLocationManager } from '@kit.LocationKit';

const MAP_VIEW_NAME = 'LocationDemo';

// 获取应用上下文
let globalContext: Context;

/**
 * 定位模式选项
 */
interface LocationModeOption {
  name: string;
  mode: number;
}

@Entry
@Component
struct Demo04_Location {
  private mapView: MapView | undefined = undefined;
  private aMap: AMap | undefined = undefined;
  private locationStyle: MyLocationStyle = new MyLocationStyle();
  private locationIntervalId: number = 0;
  
  @State isMapReady: boolean = false;
  @State hasLocationPermission: boolean = false;
  @State isLocationEnabled: boolean = false;
  @State currentMode: string = '只展示';
  @State locationInfo: string = '等待定位...';
  @State permissionStatus: string = '检查权限中...';
  
  // 定位模式选项
  private locationModes: LocationModeOption[] = [
    { name: '只展示', mode: MyLocationStyle.LOCATION_TYPE_SHOW },
    { name: '定位一次', mode: MyLocationStyle.LOCATION_TYPE_LOCATE },
    { name: '跟随', mode: MyLocationStyle.LOCATION_TYPE_FOLLOW },
    { name: '地图旋转', mode: MyLocationStyle.LOCATION_TYPE_MAP_ROTATE },
    { name: '定位点旋转', mode: MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE }
  ];

  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(async (map: AMap) => {
        this.aMap = map;
        this.isMapReady = true;
        
        // 启用缩放控件
        map.getUiSettings()?.setZoomControlsEnabled(true);
        
        // 检查权限并配置定位
        await this.checkAndRequestPermission();
      });
    };

  /**
   * 检查并请求定位权限
   */
  private async checkAndRequestPermission(): Promise<void> {
    try {
      const permission: Permissions = 'ohos.permission.LOCATION';
      const grantStatus = await this.checkAccessToken(permission);
      
      if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        this.hasLocationPermission = true;
        this.permissionStatus = '✅ 定位权限已授予';
        // 配置定位
        await this.configureLocation();
      } else {
        this.permissionStatus = '⏳ 正在请求权限...';
        // 请求权限
        await this.requestPermissions();
      }
    } catch (error) {
      console.error('[Location] Permission check failed:', error);
      this.permissionStatus = '❌ 权限检查失败';
    }
  }

  /**
   * 检查权限状态
   */
  private async checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
    const atManager = abilityAccessCtrl.createAtManager();
    let grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
      );
      const tokenId = bundleInfo.appInfo.accessTokenId;
      grantStatus = await atManager.checkAccessToken(tokenId, permission);
    } catch (error) {
      console.error('[Location] Check token failed:', error);
    }
    
    return grantStatus;
  }

  /**
   * 请求权限
   */
  private async requestPermissions(): Promise<void> {
    const permissions: Permissions[] = [
      'ohos.permission.APPROXIMATELY_LOCATION',
      'ohos.permission.LOCATION'
    ];
    
    try {
      const context = getContext(this) as common.UIAbilityContext;
      const atManager = abilityAccessCtrl.createAtManager();
      
      const result = await atManager.requestPermissionsFromUser(context, permissions);
      
      const allGranted = result.authResults.every(status => status === 0);
      
      if (allGranted) {
        this.hasLocationPermission = true;
        this.permissionStatus = '✅ 定位权限已授予';
        await this.configureLocation();
      } else {
        this.hasLocationPermission = false;
        this.permissionStatus = '❌ 用户拒绝了定位权限';
      }
    } catch (error) {
      console.error('[Location] Request permission failed:', error);
      this.permissionStatus = '❌ 权限请求失败';
    }
  }

  /**
   * 配置定位蓝点
   */
  private async configureLocation(): Promise<void> {
    if (!this.aMap) return;
    
    try {
      // 设置定位样式
      this.locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_SHOW);
      
      // 自定义定位图标(可选)
      try {
        const icon = await BitmapDescriptorFactory.fromRawfilePath(
          globalContext,
          'location_icon.png'
        );
        if (icon) {
          this.locationStyle.myLocationIcon(icon);
        }
      } catch (e) {
        console.info('[Location] Using default location icon');
      }
      
      // 设置精度圈样式
      this.locationStyle.strokeColor(0x800000FF);  // 边框颜色(带透明度的蓝色)
      this.locationStyle.strokeWidth(2);            // 边框宽度
      this.locationStyle.radiusFillColor(0x200000FF); // 填充颜色
      
      // 应用样式
      this.aMap.setMyLocationStyle(this.locationStyle);
      
      // 设置定位数据源
      this.aMap.setLocationSource(this);
      
      // 启用定位图层
      this.aMap.setMyLocationEnabled(true);
      this.isLocationEnabled = true;
      
      this.locationInfo = '定位已启用,等待位置更新...';
      console.info('[Location] Location configured successfully');
      
    } catch (error) {
      console.error('[Location] Configure location failed:', error);
      this.locationInfo = '定位配置失败';
    }
  }

  /**
   * LocationSource接口实现 - 激活定位
   */
  activate(listener: OnLocationChangedListener): void {
    console.info('[Location] activate called');
    
    // 使用系统定位获取位置
    this.startSystemLocation(listener);
  }

  /**
   * LocationSource接口实现 - 停用定位
   */
  deactivate(): void {
    console.info('[Location] deactivate called');
    
    if (this.locationIntervalId > 0) {
      clearInterval(this.locationIntervalId);
      this.locationIntervalId = 0;
    }
  }

  /**
   * 启动系统定位
   */
  private startSystemLocation(listener: OnLocationChangedListener): void {
    // 定位请求配置
    const requestInfo: geoLocationManager.CurrentLocationRequest = {
      priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
      scenario: geoLocationManager.LocationRequestScenario.UNSET,
      maxAccuracy: 100
    };
    
    // 获取当前位置
    geoLocationManager.getCurrentLocation(requestInfo)
      .then((location: geoLocationManager.Location) => {
        console.info('[Location] Got location:', location.latitude, location.longitude);
        
        // 转换为高德定位格式
        const amapLocation: geoLocationManager.Location = {
          latitude: location.latitude,
          longitude: location.longitude,
          altitude: location.altitude,
          accuracy: location.accuracy,
          speed: location.speed,
          direction: location.direction,
          timeStamp: location.timeStamp,
          timeSinceBoot: location.timeSinceBoot,
          altitudeAccuracy: location.altitudeAccuracy || 0,
          speedAccuracy: location.speedAccuracy || 0,
          directionAccuracy: location.directionAccuracy || 0,
          uncertaintyOfTimeSinceBoot: location.uncertaintyOfTimeSinceBoot || 0,
          sourceType: location.sourceType || 1
        };
        
        // 更新位置到地图
        listener.onLocationChanged(amapLocation);
        
        // 更新UI显示
        this.locationInfo = `经度: ${location.longitude.toFixed(6)}\n纬度: ${location.latitude.toFixed(6)}\n精度: ${location.accuracy?.toFixed(0) || '未知'}米`;
        
        // 移动地图到当前位置
        if (this.aMap) {
          const latLng = new LatLng(location.latitude, location.longitude);
          this.aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16));
        }
      })
      .catch((error: BusinessError) => {
        console.error('[Location] Get location failed:', error.message);
        this.locationInfo = '获取位置失败: ' + error.message;
        
        // 使用模拟位置(开发测试用)
        this.useSimulatedLocation(listener);
      });
    
    // 设置定时更新(可选)
    if (this.locationIntervalId === 0) {
      this.locationIntervalId = setInterval(() => {
        geoLocationManager.getCurrentLocation(requestInfo)
          .then((location) => {
            const amapLocation: geoLocationManager.Location = {
              latitude: location.latitude,
              longitude: location.longitude,
              altitude: location.altitude,
              accuracy: location.accuracy,
              speed: location.speed,
              direction: location.direction,
              timeStamp: location.timeStamp,
              timeSinceBoot: location.timeSinceBoot,
              altitudeAccuracy: location.altitudeAccuracy || 0,
              speedAccuracy: location.speedAccuracy || 0,
              directionAccuracy: location.directionAccuracy || 0,
              uncertaintyOfTimeSinceBoot: location.uncertaintyOfTimeSinceBoot || 0,
              sourceType: location.sourceType || 1
            };
            listener.onLocationChanged(amapLocation);
            this.locationInfo = `经度: ${location.longitude.toFixed(6)}\n纬度: ${location.latitude.toFixed(6)}\n精度: ${location.accuracy?.toFixed(0) || '未知'}米`;
          })
          .catch(() => {});
      }, 5000);
    }
  }

  /**
   * 使用模拟位置(开发测试用)
   */
  private useSimulatedLocation(listener: OnLocationChangedListener): void {
    // 模拟北京位置
    const simulatedLocation: geoLocationManager.Location = {
      latitude: 39.909187,
      longitude: 116.397451,
      altitude: 50,
      accuracy: 100,
      speed: 0,
      direction: 0,
      timeStamp: Date.now(),
      timeSinceBoot: 0,
      altitudeAccuracy: 0,
      speedAccuracy: 0,
      directionAccuracy: 0,
      uncertaintyOfTimeSinceBoot: 0,
      sourceType: 1
    };
    
    listener.onLocationChanged(simulatedLocation);
    this.locationInfo = `[模拟] 经度: ${simulatedLocation.longitude.toFixed(6)}\n纬度: ${simulatedLocation.latitude.toFixed(6)}`;
    
    if (this.aMap) {
      const latLng = new LatLng(simulatedLocation.latitude, simulatedLocation.longitude);
      this.aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16));
    }
  }

  /**
   * 切换定位模式
   */
  private setLocationMode(option: LocationModeOption): void {
    if (!this.aMap) return;
    
    this.locationStyle.myLocationType(option.mode);
    this.aMap.setMyLocationStyle(this.locationStyle);
    this.currentMode = option.name;
    
    console.info('[Location] Mode changed to:', option.name);
  }

  /**
   * 切换定位开关
   */
  private toggleLocation(): void {
    if (!this.aMap) return;
    
    this.isLocationEnabled = !this.isLocationEnabled;
    this.aMap.setMyLocationEnabled(this.isLocationEnabled);
    
    if (!this.isLocationEnabled) {
      this.locationInfo = '定位已关闭';
    }
  }

  aboutToAppear(): void {
    globalContext = getContext(this).getApplicationContext();
    MapViewManager.getInstance()
      .registerMapViewCreatedCallback(this.mapViewCreateCallback);
  }

  aboutToDisappear(): void {
    // 停止定位更新
    if (this.locationIntervalId > 0) {
      clearInterval(this.locationIntervalId);
      this.locationIntervalId = 0;
    }
    
    // 关闭定位
    if (this.aMap) {
      this.aMap.setMyLocationEnabled(false);
    }
    
    MapViewManager.getInstance()
      .unregisterMapViewCreatedCallback(this.mapViewCreateCallback);
    
    if (this.mapView) {
      this.mapView.onDestroy();
      this.mapView = undefined;
      this.aMap = undefined;
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('定位蓝点功能')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(50)
      .padding({ left: 16 })
      .backgroundColor('#03A9F4')

      // 地图区域
      Stack() {
        MapViewComponent({ mapViewName: MAP_VIEW_NAME })
          .width('100%')
          .height('100%')
        
        // 状态信息面板
        Column() {
          Text('定位状态')
            .fontSize(12)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333')
          Text(this.permissionStatus)
            .fontSize(11)
            .fontColor('#666')
            .margin({ top: 4 })
          Text(this.locationInfo)
            .fontSize(11)
            .fontColor('#666')
            .margin({ top: 4 })
          Text(`当前模式: ${this.currentMode}`)
            .fontSize(11)
            .fontColor('#2196F3')
            .margin({ top: 4 })
        }
        .padding(10)
        .backgroundColor('rgba(255,255,255,0.95)')
        .borderRadius(8)
        .position({ x: 10, y: 10 })
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .layoutWeight(1)

      // 控制面板
      Column() {
        // 定位开关
        Row() {
          Text('定位蓝点')
            .fontSize(14)
            .fontColor('#333')
          Blank()
          Toggle({ type: ToggleType.Switch, isOn: this.isLocationEnabled })
            .enabled(this.hasLocationPermission)
            .onChange((isOn: boolean) => {
              this.isLocationEnabled = isOn;
              this.aMap?.setMyLocationEnabled(isOn);
            })
        }
        .width('100%')
        .height(44)
        .padding({ left: 12, right: 12 })
        .backgroundColor(Color.White)
        .borderRadius(8)
        .margin({ bottom: 8 })

        // 定位模式选择
        Text('定位模式')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .margin({ bottom: 8 })

        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.locationModes, (option: LocationModeOption) => {
            Button(option.name)
              .fontSize(11)
              .height(32)
              .backgroundColor(this.currentMode === option.name ? '#03A9F4' : '#e0e0e0')
              .fontColor(this.currentMode === option.name ? Color.White : '#333')
              .margin({ right: 6, bottom: 6 })
              .enabled(this.hasLocationPermission && this.isLocationEnabled)
              .onClick(() => this.setLocationMode(option))
          })
        }

        // 重新定位按钮
        Button('重新定位')
          .width('100%')
          .height(40)
          .margin({ top: 8 })
          .enabled(this.hasLocationPermission)
          .onClick(() => this.configureLocation())
      }
      .padding(12)
      .backgroundColor('#f5f5f5')
    }
    .width('100%')
    .height('100%')
  }
}

4. MyLocationStyle 完整API

typescript 复制代码
class MyLocationStyle {
  // 设置定位模式
  myLocationType(type: number): MyLocationStyle;
  
  // 设置定位图标
  myLocationIcon(icon: BitmapDescriptor): MyLocationStyle;
  
  // 设置定位间隔(毫秒)
  interval(interval: number): MyLocationStyle;
  
  // 精度圈边框颜色
  strokeColor(color: number): MyLocationStyle;
  
  // 精度圈边框宽度
  strokeWidth(width: number): MyLocationStyle;
  
  // 精度圈填充颜色
  radiusFillColor(color: number): MyLocationStyle;
  
  // 是否显示精度圈
  showMyLocation(show: boolean): MyLocationStyle;
}

5. 颜色值说明

高德地图使用ARGB格式的颜色值:

typescript 复制代码
// 格式: 0xAARRGGBB
// AA: 透明度 (00=完全透明, FF=完全不透明)
// RR: 红色
// GG: 绿色
// BB: 蓝色

// 示例
0x800000FF  // 半透明蓝色
0x200000FF  // 较透明的蓝色
0xFF00FF00  // 不透明绿色

6. 实用技巧

6.1 判断定位服务是否可用

typescript 复制代码
import { geoLocationManager } from '@kit.LocationKit';

const isEnabled = geoLocationManager.isLocationEnabled();
if (!isEnabled) {
  // 提示用户开启定位服务
}

6.2 引导用户开启权限

typescript 复制代码
import { common } from '@kit.AbilityKit';

// 跳转到应用设置页面
const context = getContext(this) as common.UIAbilityContext;
const want: common.Want = {
  bundleName: 'com.huawei.hmos.settings',
  abilityName: 'com.huawei.hmos.settings.MainAbility',
  uri: 'application_info_entry',
  parameters: {
    pushParams: context.abilityInfo.bundleName
  }
};
context.startAbility(want);

本篇小结

本篇教程我们学习了:

  • ✅ 定位权限的申请和检查
  • ✅ 定位蓝点的显示与配置
  • ✅ 多种定位模式的切换
  • ✅ 自定义定位蓝点样式
  • ✅ LocationSource接口实现

下一篇我们将学习POI搜索功能。

班级 developer.huawei.com/consumer/cn...

源码地址 gitcode.com/daleishen/g...

相关推荐
汉堡黄•᷄ࡇ•᷅2 小时前
鸿蒙开发:案例集合List:ListItem拖拽(交换位置,过渡动画)(性能篇)
华为·harmonyos·鸿蒙·鸿蒙系统
HONG````3 小时前
鸿蒙应用动态文件读取全指南:从JSON、XML到文本文件的解析实战
华为·harmonyos
Mr_Hu4044 小时前
鸿蒙开发学习笔记-生命周期小记
笔记·学习·harmonyos·鸿蒙
汉堡黄4 小时前
鸿蒙开发:案例集合List:ListItem拖拽(交换位置,过渡动画)(性能篇)
harmonyos
食品一少年6 小时前
开源鸿蒙 PC · Termony 自验证环境搭建与外部 HNP 集成实践(DAY4-10)(2)
华为·harmonyos
waeng_luo6 小时前
[鸿蒙2025领航者闯关] 鸿蒙应用中如何管理组件状态?
前端·harmonyos·鸿蒙·鸿蒙2025领航者闯关·鸿蒙6实战·开发者年度总结
不老刘6 小时前
HarmonyOS ArkTS IconFont 实践指南
harmonyos·鸿蒙·iconfont
盐焗西兰花17 小时前
鸿蒙学习实战之路:Tabs 组件开发场景最佳实践
学习·华为·harmonyos
盐焗西兰花17 小时前
鸿蒙学习实战之路 - 瀑布流操作实现
学习·华为·harmonyos