找回密码
 立即注册
首页 业界区 业界 鸿蒙运动开发实战:打造 Keep 式轨迹播放效果 ...

鸿蒙运动开发实战:打造 Keep 式轨迹播放效果

宛蛲 2025-6-3 09:56:13
前言
在运动类应用中,轨迹播放效果是提升用户体验的关键功能之一。它不仅能直观展示用户的运动路线,还能通过动态效果增强运动的趣味性。Keep 作为一款知名的运动健身应用,其轨迹播放效果深受用户喜爱。那么,如何在鸿蒙系统中开发出类似 Keep 的轨迹播放效果呢?本文将通过实际代码案例,深入解析实现这一功能的关键步骤和技术要点。
效果:
1.gif

一、核心功能拆解
要实现类似 Keep 的轨迹播放效果,我们需要完成以下几个核心功能:
• 动态轨迹播放:通过定时器和动画效果,实现轨迹的动态播放,模拟用户运动过程。
• 地图交互:在地图上绘制轨迹,并根据播放进度更新地图中心点和旋转角度。
二、动态轨迹播放
1.播放逻辑
通过定时器和动画效果实现轨迹的动态播放。以下是播放轨迹的核心代码:
  1. private playTrack() {
  2.   // 如果已经在播放,则停止
  3.   if (this.playTimer) {
  4.     this.mapController?.removeOverlay(this.polyline);
  5.     clearInterval(this.playTimer);
  6.     this.playTimer = undefined;
  7.     if (this.animationTimer) {
  8.       clearInterval(this.animationTimer);
  9.     }
  10.     if (this.movingMarker) {
  11.       this.mapController?.removeOverlay(this.movingMarker);
  12.       this.movingMarker = undefined;
  13.     }
  14.     this.currentPointIndex = 0;
  15.     return;
  16.   }
  17.   // 创建动态位置标记
  18.   this.movingMarker = new Marker({
  19.     position: this.trackPoints[0],
  20.     icon: new ImageEntity("rawfile://images/ic_run_detail_start.png"),
  21.     isJoinCollision: SysEnum.CollisionBehavior.NOT_COLLIDE,
  22.     located: SysEnum.Located.CENTER
  23.   });
  24.   this.mapController?.addOverlay(this.movingMarker);
  25.   // 开始播放
  26.   this.playTimer = setInterval(() => {
  27.     this.currentPointIndex++;
  28.     if (this.currentPointIndex >= this.trackPoints.length) {
  29.       clearInterval(this.playTimer);
  30.       this.playTimer = undefined;
  31.       this.currentPointIndex = 0;
  32.       if (this.movingMarker) {
  33.         this.mapController?.removeOverlay(this.movingMarker);
  34.         this.movingMarker = undefined;
  35.       }
  36.       return;
  37.     }
  38.     // 更新动态位置标记位置,使用setInterval实现平滑移动
  39.     if (this.movingMarker && this.currentPointIndex < this.trackPoints.length - 1) {
  40.       const currentPoint = this.trackPoints[this.currentPointIndex];
  41.       const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  42.       let animationProgress = 0;
  43.       // 清除之前的动画定时器
  44.       if (this.animationTimer) {
  45.         clearInterval(this.animationTimer);
  46.       }
  47.       // 创建新的动画定时器,每10ms更新一次位置
  48.       this.animationTimer = setInterval(() => {
  49.         animationProgress += 0.1; // 每次增加0.1的进度
  50.         if (animationProgress >= 1) {
  51.           clearInterval(this.animationTimer);
  52.           this.animationTimer = undefined;
  53.           this.movingMarker?.setPosition(new LatLng(nextPoint.lat, nextPoint.lng));
  54.         } else {
  55.           const interpolatedLat = currentPoint.lat + (nextPoint.lat - currentPoint.lat) * animationProgress;
  56.           const interpolatedLng = currentPoint.lng + (nextPoint.lng - currentPoint.lng) * animationProgress;
  57.           this.movingMarker?.setPosition(new LatLng(interpolatedLat, interpolatedLng));
  58.         }
  59.       }, 10); // 每10ms执行一次
  60.     }
  61.     // 绘制当前轨迹线段
  62.     const currentPoints = this.trackPoints.slice(0, this.currentPointIndex + 1);
  63.     const currentColors = PathGradientTool.getPathColors(this.record!.points.slice(0, this.currentPointIndex + 1), 100);
  64.     if (this.polyline) {
  65.       this.mapController?.removeOverlay(this.polyline);
  66.       this.polyline.remove();
  67.       this.polyline.destroy();
  68.     }
  69.     this.polyline = new Polyline({
  70.       points: currentPoints,
  71.       width: 5,
  72.       join: SysEnum.LineJoinType.ROUND,
  73.       cap: SysEnum.LineCapType.ROUND,
  74.       isGradient: true,
  75.       colorList: currentColors!
  76.     });
  77.     this.mapController?.addOverlay(this.polyline);
  78.     // 更新地图中心点和旋转角度
  79.     let bearing = 0;
  80.     if (this.currentPointIndex < this.trackPoints.length - 1) {
  81.       const currentPoint = this.trackPoints[this.currentPointIndex];
  82.       const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  83.       bearing = Math.atan2(
  84.         nextPoint.lat - currentPoint.lat,
  85.         nextPoint.lng - currentPoint.lng
  86.       ) * 180 / Math.PI;
  87.       bearing = (bearing + 360) % 360;
  88.       bearing = (360 - bearing + 90) % 360;
  89.     }
  90.     this.mapController?.mapStatus.setRotate(bearing).setOverlooking(90).setCenterPoint(new LatLng(this.trackPoints[this.currentPointIndex].lat, this.trackPoints[this.currentPointIndex].lng)).refresh();
  91.   }, 100); // 每100ms移动一次
  92. }
复制代码
2.动画效果
通过定时器和线性插值实现动态轨迹的平滑移动效果。以下是动画效果的核心代码:
  1. if (this.movingMarker && this.currentPointIndex < this.trackPoints.length - 1) {
  2.   const currentPoint = this.trackPoints[this.currentPointIndex];
  3.   const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  4.   let animationProgress = 0;
  5.   // 清除之前的动画定时器
  6.   if (this.animationTimer) {
  7.     clearInterval(this.animationTimer);
  8.   }
  9.   // 创建新的动画定时器,每10ms更新一次位置
  10.   this.animationTimer = setInterval(() => {
  11.     animationProgress += 0.1; // 每次增加0.1的进度
  12.     if (animationProgress >= 1) {
  13.       clearInterval(this.animationTimer);
  14.       this.animationTimer = undefined;
  15.       this.movingMarker?.setPosition(new LatLng(nextPoint.lat, nextPoint.lng));
  16.     } else {
  17.       const interpolatedLat = currentPoint.lat + (nextPoint.lat - currentPoint.lat) * animationProgress;
  18.       const interpolatedLng = currentPoint.lng + (nextPoint.lng - currentPoint.lng) * animationProgress;
  19.       this.movingMarker?.setPosition(new LatLng(interpolatedLat, interpolatedLng));
  20.     }
  21.   }, 10); // 每10ms执行一次
  22. }
复制代码
三、地图交互
1.地图中心点和旋转角度更新
在播放轨迹的过程中,动态更新地图的中心点和旋转角度,以确保用户始终能看到当前播放的位置。以下是更新地图中心点和旋转角度的代码:
  1. let bearing = 0;
  2. if (this.currentPointIndex < this.trackPoints.length - 1) {
  3.   const currentPoint = this.trackPoints[this.currentPointIndex];
  4.   const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  5.   bearing = Math.atan2(
  6.     nextPoint.lat - currentPoint.lat,
  7.     nextPoint.lng - currentPoint.lng
  8.   ) * 180 / Math.PI;
  9.   bearing = (bearing + 360) % 360;
  10.   bearing = (360 - bearing + 90) % 360;
  11. }
  12. this.mapController?.mapStatus.setRotate(bearing).setOverlooking(90).setCenterPoint(new LatLng(this.trackPoints[this.currentPointIndex].lat, this.trackPoints[this.currentPointIndex].lng)).refresh();
复制代码
四、总结
通过上述步骤,我们成功实现了类似 Keep 的轨迹播放效果。不仅提升了用户体验,还为运动数据的可视化提供了有力支持。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册