第五篇:定位蓝点功能
本篇教程将学习如何在地图上显示用户当前位置(定位蓝点),包括权限申请、定位样式自定义等。
学习目标
- 申请定位权限
- 显示定位蓝点
- 自定义定位蓝点样式
- 理解不同的定位模式
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搜索功能。