鸿蒙开发实战案例——地图定位打卡案例

介绍

本示例使用 geoLocationManager 进行地理位置定位和地理信息获取,并利用 MapComponent 组件展示地图,添加用户位置和打卡范围,通过计算用户位置和打卡中心点的距离判断用户是否处于打卡区域,实现了打卡功能。

效果图预览

使用说明

  1. 首次启动应用时,授权获取定位权限,地图会移动到用户的当前位置,并在用户位置显示标记和打卡区域。

  2. 点击右下角定位按钮,地图会移动到用户当前位置。

  3. 点击上班打卡,如果用户处于打卡范围内,界面显示上班打卡信息,否则提示“打卡范围外,请先前往打卡范围打卡”。

  4. 点击下班打卡,如果用户处于打卡范围内,界面显示下班打卡信息,否则提示“打卡范围外,请先前往打卡范围打卡”。

  5. 点击更新打卡,如果用户处于打卡范围内,界面更新下班打卡信息,否则提示“打卡范围外,请先前往打卡范围打卡”。

注意:本示例需要打开位置定位和联网使用。 

开发准备

使用本示例前,需要先完成以下准备工作。

1.在华为开发者联盟网站上,注册成为开发者,并完成实名认证,从而享受联盟开放的各类能力和服务。

2.在AppGallery Connect(简称AGC)上,参考创建项目和创建应用完成HarmonyOS应用的创建,从而使用各类服务。

3.登录AppGallery Connect平台,在“我的项目”中选择目标应用,参考配置Client ID将应用的Client ID配置到工程中entry模块的module.json5文件中,然后在AGC平台开通地图服务。

4.连接设备后,工程使用自动签名生成数字证书(.cer),在AGC网站的“证书、APP ID和Profile”页签中可以查看生成的调试证书。

5.在AppGallery Connect(简称AGC)上,参考添加公钥指纹(HarmonyOS API9及以上)为应用添加公钥指纹,指纹配置成功后大约10分钟左右,设备联网即可使用地图服务。

实现思路

ClockInComponent是一个用于显示地图、获取用户位置、判断用户是否在打卡范围内的自定义功能组件。它使用了MapComponent组件来显示地图,geoLocationManager接口来获取用户位置,并通过计算用户位置与打卡圆心之间的距离来判断用户是否在打卡范围内。

1.使用MapComponent组件初始化地图,设置地图初始位置和缩放级别。

  // 地图初始化参数,设置地图中心点坐标及层级
  mapOptions: mapCommon.MapOptions = {
    position: {
      target: {
        latitude: 39.9,
        longitude: 116.4
      },
      zoom: 14
    }
  };

  MapComponent({
    mapOptions: this.mapOptions,
    mapCallback: this.mapCallback,
    customInfoWindow: (markerDelegate: map.MarkerDelegate) => {
      this.customInfoWindowBuilder(markerDelegate);
    }
  })

在地图上绘制打卡范围和位置标记。 

  • 在地图初始化回调函数中获取地图控制器和地图监听事件管理器,隐藏缩放控件和设置地图和边界的距离

    initMap() {
      // 地图初始化的回调
      this.mapCallback = async (err, mapController) => {
        if (!err) {
          // 获取地图控制器,用来操作地图
          this.mapController = mapController;
          if (this.getMapController) {
            this.getMapController(this.mapController);
          }
          // 获取地图监听事件管理器
          this.mapEventManager = this.mapController.getEventManager();
          // 隐藏缩放控件
          this.mapController.setZoomControlsEnabled(false);
          // 设置地图和边界的距离
          this.mapController.setPadding(this.mapPadding);
          // ...
        } else {
          logger.error(TAG, `mapcomponent init failed, error message: ${err.message}, error code: ${err.code}`);
        }
      };
    }
  • 监听地图加载完成事件,地图加载完成回调中验证应用是否拥有定位权限,无权限则弹窗向用户申请授权

    initMap() {
      // 地图初始化的回调
      this.mapCallback = async (err, mapController) => {
        if (!err) {
          // ...
          let callback = () => {
            // 验证用户是否授予定位权限,无权限则在首次启动时弹出权限申请对话框
            this.reqPermissionFromUser().then(async (grantStatus) => {
              // ...
            }).catch((err: BusinessError) => {
              logger.error(TAG, `permission request failed, error message: ${err.message}, error code: ${err.code}`);
            });
          }
          // 监听地图加载完成事件
          this.mapEventManager.on('mapLoad', callback);
        } else {
          logger.error(TAG, `mapcomponent init failed, error message: ${err.message}, error code: ${err.code}`);
        }
      };
    }          
  • 权限验证通过后获取用户当前位置,并移动相机使当前位置处在地图可见区域中心,然后在地图上绘制打卡范围和位置标记。

    if (grantStatus) {
      // 获取用户位置
      await this.getUserCurrentLocation();
      // 动画方式移动相机到用户位置,动画过程中设置显示标记信息窗会失效,需要等待动画结束
      await this.animateMoveCamera(this.myPositionGCJ02, this.duration);
      // 添加圆形打卡区域
      await this.addCircle();
      // 添加用户位置标记
      this.marker = await this.mapController?.addMarker({
        icon: this.markerIcon,
        position: this.myPositionGCJ02,
        draggable: false,
        visible: true,
        clickable: true, // 要显示信息窗口必须为true
        zIndex: 15,
        alpha: 1,
        anchorU: 0.5,
        anchorV: 1,
        rotation: 0
      });
      // ...
    }
  • 用户当前位置使用getCurrentLocation接口获取,获取的结果是WGS84坐标系经纬度,地图使用的是GCJ02坐标系,需要使用convertCoordinateSync接口进行转换。

    /**
     * 获取用户当前位置。
     */
    async getUserCurrentLocation() {
      this.myPositionWGS84 = await geoLocationManager.getCurrentLocation();
      this.myPositionGCJ02 =
        map.convertCoordinateSync(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, this.myPositionWGS84);
    }
  • 使用calculateDistance接口计算用户位置和打卡中心点的距离,判断用户是否在打卡范围内,并保存结果。

  /**
   * 判断用户是否在圆圈内。
   * @param {LatLng} pos1 - 用户位置。
   * @param {LatLng} pos2 - 圆心位置。
   * @param {number} radius - 圆半径,单位为米。
   */
  isUserInCircle(pos1: mapCommon.LatLng, pos2: mapCommon.LatLng, radius: number) {
    const distance = map.calculateDistance(pos1, pos2);
    this.isInArea = distance <= radius;
  }
  • 使用getAddressesFromLocation接口根据WGS84坐标系经纬度获取用户的地理位置信息,设置为marker标题并显示marker信息窗口。

  /**
   * 设置标记信息。
   */
  setMarkerInfo() {
    if (this.myPositionWGS84) {
      let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest = {
        latitude: this.myPositionWGS84.latitude,
        longitude: this.myPositionWGS84.longitude,
        maxItems: 1 // 获取最近的一个地址
      };
      // 将坐标转换为地理描述
      geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest).then((data) => {
        if (this.marker && data[0].placeName) {
          // 显示marker信息窗
          this.marker.setTitle(data[0].placeName);
          this.marker.setInfoWindowVisible(true);
        }
      }).catch((err: BusinessError) => {
        logger.error(TAG, `addresser get failed, error message: ${err.message}, error code: ${err.code}`);
      });
    }
  }
  • 当变量isAddLocationListener值为true时添加位置监听,用户位置发生变动时,更新用户位置和地图标记,并重新判断用户是否在打卡范围内。

  /**
   * 添加用户位置监听。
   */
  addLocationListener() {
    let requestInfo: geoLocationManager.LocationRequest = {
      priority: geoLocationManager.LocationRequestPriority.ACCURACY,
      scenario: geoLocationManager.LocationRequestScenario.UNSET,
      timeInterval: this.locationTimeInterval,
      distanceInterval: 0,
      maxAccuracy: 0
    };
    let locationChange = (location: geoLocationManager.Location): void => {
      this.myPositionWGS84 = location;
      this.myPositionGCJ02 =
        map.convertCoordinateSync(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, location);
      if (this.marker) {
        this.animateMoveMarker(this.marker, this.myPositionGCJ02, this.duration);
      }
      // 位置变动时再次判断用户是否在打卡范围内
      if (this.mapCircleOptions) {
        this.isUserInCircle(this.myPositionGCJ02, this.mapCircleOptions.center, this.mapCircleOptions.radius);
      }
    };
    geoLocationManager.on('locationChange', requestInfo, locationChange);
  }
  • 定义ClockInController类,当接收到父组件传入的实例clockInController给属性getAddress赋值,父组件即可通过clockInController实例调用ClockInComponent中的getAddress箭头函数动态获取当前用户打卡所处地址。

  // ClockInModel.ets
  /**
   * ClockInController 类用于处理打卡相关的逻辑。
   * 该控制器负责获取当前位置地址等功能。
   */
  export class ClockInController {
    /**
     * 获取当前位置地址的方法。
     * @returns {string} 当前位置的地址字符串。
     */
    getAddress: () => string = () => '';
  }
  // ClockInComponent.ets
  /**
   * 获取当前位置地址。
   * @returns {string} 当前位置的地址字符串。
   */
  private getAddress = () => {
    if (this.marker) {
      return this.marker.getTitle();
    }
    return '';
  }

ClockInSamplePage基于ClockInComponent实现了完整的打卡场景,可通过传入参数控制打卡区域的位置和样式、定位按钮的显示隐藏、是否开启位置监听、用户位置信息窗口的内容和样式等,并且根据ClockInComponent中用户位置是否处于打卡范围的判断结果进行打卡操作及反馈。

  • 引入功能组件ClockInComponent,并初始化相关属性,其中isInArea为必填参数,用于同步用户位置是否处于打卡范围的判断结果。

  ClockInComponent({
    clockInController: this.clockInController,
    isInArea: this.isInArea,
    isLocationButtonVisible: this.isLocationButtonVisible,
    locationButtonPosition: this.locationButtonPosition,
    mapOptions: this.mapOptions,
    getMapController: this.getController,
    customInfoWindowSlotParam: this.customInfoWindowSlot
  })
  • 处理打卡逻辑。当用户处于打卡范围内时,提供打卡功能,并记录打卡信息,否则打卡失败,提示用户进入打卡范围后打卡。其中地址信息可通过传入ClockInComponent的实例clockInController调用getAddress获取。

  // 打卡按钮
  Text(this.clockInButtonText)
    .onClick(() => {
      // 如果在打卡范围内,进行打卡操作,否则显示提示信息
      if (this.isInArea) {
        // 如果已有上班打卡信息,则进行下班打卡,否则新增上班打卡信息
        if (this.clockInInfo) {
          // 如果已有下班打卡信息,则更新下班打卡信息,否则新增下班打卡信息
          this.clockOutInfo = {
            time: this.timeFormat.format(new Date()),
            address: this.clockInController.getAddress()
          };
          this.clockInButtonText = this.clockOutInfo !== null ? $r('app.string.clock_in_button_text_update') :
          $r('app.string.clock_in_button_text_clock_out');
        } else {
          this.clockInInfo = {
            time: this.timeFormat.format(new Date()),
            address: this.clockInController.getAddress()
          };
          this.clockInButtonText = $r('app.string.clock_in_button_text_clock_out');
        }
      } else {
        promptAction.showToast({
          message: $r('app.string.clock_in_toast_message_out_of_range')
        });
      }
    })
    // ...

高性能知识点

不涉及

工程结构&模块类型

   clockin                                  // har类型
   |---/src/main/ets/components                       
   |   |---ClockInComponent.ets             // 封装的打卡功能组件
   |---/src/main/ets/model                        
   |   |---ClockInModel.ets                 // 数据模型层-打卡功能组件控制器与打卡信息数据模型 
   |---/src/main/ets/utils                        
   |   |---Constants.ets                    // 常量数据
   |   |---Logger.ets                       // 日志打印工具
   |---/src/main/ets/views                        
   |   |---ClockInSamplePage.ets            // 视图层-打卡场景主页面

模块依赖 

本示例依赖动态路由模块来实现页面的动态加载。

内容概要:文章全面介绍了鸿蒙next定位服务的开发实践,涵盖定位基础、四大核心场景开发实战、常见问题排查和性能优化等方面。首先解析了两种核心定位方式(GNSS定位与网络定位)的技术原理和应用场景,强调了根据业务需求选择合适的定位策略的重要性。接着详细讲解了四大核心场景的开发实战,包括当前位置定位、实时位置追踪、后台持续获取定位和历史定位获取,提供了完整的代码示例和开发步骤。此外,针对定位不准、定位失败、缓存位置不一致等常见问题进行了详细的排查和解决方案说明。最后,提出了定位策略动态调整、资源及时释放和功耗测试等性能优化方法,帮助开发者提高应用的性能和用户体验。 适用人群:具备一定编程基础,特别是对鸿蒙系统开发感兴趣的开发者,以及希望深入了解位置服务实现原理的技术人员。 使用场景及目标:①学习如何根据不同的业务需求选择GNSS定位或网络定位;②掌握四大核心场景的定位开发技巧,如当前位置获取、实时位置追踪、后台持续定位和历史定位获取;③理解常见问题的排查方法,如定位不准、定位失败等问题的解决方案;④学习如何通过定位策略动态调整、资源及时释放等方式优化应用性能和功耗。 阅读建议:本文内容详实,涉及多个定位开发的实际操作和优化技巧,建议读者在阅读过程中结合具体的开发场景进行实践,并利用提供的代码示例进行调试和测试,以便更好地理解和掌握鸿蒙next定位服务的开发要点。
<think>嗯,用户想在鸿蒙系统中实现定位打卡功能,使用HMS Location Kit。首先我需要回忆一下HMS Location Kit的相关功能。根据引用[1],定位服务用了GNSS、Wi-Fi、基站混合定位,能快速精准获取位置。所以HMS Location Kit应该能提供这些能力。 用户的需求是打卡功能,可能需要获取当前位置,然后判断是否在指定范围内。可能需要用到地理围栏或者位置监听。另外,引用[3]提到的Awareness Kit可能也有相关的情景感知能力,比如位置和活动状态,或许可以结合使用。 步骤方面,首先应该配置开发环境,添加依赖。然后检查权限,因为定位需要用户授权。接着初始化定位服务,可能需要使用Fused Location Provider。然后获取位置,可能是一次性获取或者持续监听。打卡功能需要判断是否在指定范围内,比如使用经纬度坐标和半径创建地理围栏。当用户进入围栏区域时触发打卡动作。 可能的问题包括权限处理、定位精度选择、耗电优化等。比如,引用[2]中提到用Beacon做精准推送,可能高精度定位需要结合蓝牙信标,但基本的打卡可能用GNSS就够了。另外,持续监听位置可能会耗电,所以需要考虑按需获取或者使用低功耗模式。 需要确保代码示例正确,比如在鸿蒙中使用Kotlin或Java,但用户提供的示例是Python的快速排序,可能需要调整。此外,权限申请流程也很重要,用户需要在manifest里声明权限,并且动态请求。 另外,定位打卡可能涉及后台服务,需要处理后台定位的限制,可能用到前台服务或者鸿蒙的任务管理。地理围栏的实现可能需要使用Location Kit的API来添加围栏并设置触发条件。 最后,测试时需要注意不同场景,比如室内可能依赖Wi-Fi或基站,室外用GPS。同时,处理定位失败的情况,提供错误反馈给用户。</think>### 鸿蒙系统中实现定位打卡功能的步骤(基于HMS Location Kit) #### 1. 环境配置 - **添加依赖**:在`build.gradle`中集成HMS Location Kit ```groovy dependencies { implementation 'com.huawei.hms:location:6.4.0.300' } ``` - **声明权限**:在`config.json`中添加定位权限 ```json "reqPermissions": [ {"name": "ohos.permission.LOCATION"} ] ``` #### 2. 权限动态申请 ```kotlin val permissions = arrayOf("ohos.permission.LOCATION") requestPermissionsFromUser(permissions, 0) ``` #### 3. 初始化定位服务 ```kotlin val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) ``` #### 4. 获取当前位置(单次定位) ```kotlin val locationRequest = LocationRequest().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY // 高精度模式[^1] interval = 10000 // 10秒间隔 } fusedLocationProviderClient.requestLocationUpdates(locationRequest, object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { val location = locationResult.lastLocation val latitude = location.latitude val longitude = location.longitude // 触发打卡判断逻辑 } }) ``` #### 5. 地理围栏实现 ```kotlin val geofence = Geofence.Builder() .setRoundArea(latitude, longitude, 100.0) // 以目标点为中心,100米半径范围 .setConversions(Geofence.ENTER_GEOFENCE_CONVERSION) // 进入区域触发 .build() val request = GeofenceRequest.Builder() .addGeofence(geofence) .build() LocationServices.getGeofenceService(context).createGeofenceList(request, object : GeofenceListener() { override fun onGeofenceTrigger(geofence: Geofence) { // 执行打卡记录操作 } }) ``` #### 6. 关键优化点 - **精度选择**:根据场景选择`PRIORITY_LOW_POWER`(低功耗)或`PRIORITY_HIGH_ACCURACY`(高精度)[^1] - **后台定位**:通过`createBackgroundTask()` - **位置模拟**:开发阶段使用`setMockMode(true)`进行测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值