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

前言

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

效果:

一、核心功能拆解

要实现类似 Keep 的轨迹播放效果,我们需要完成以下几个核心功能:

• 动态轨迹播放:通过定时器和动画效果,实现轨迹的动态播放,模拟用户运动过程。

• 地图交互:在地图上绘制轨迹,并根据播放进度更新地图中心点和旋转角度。

二、动态轨迹播放

1.播放逻辑

通过定时器和动画效果实现轨迹的动态播放。以下是播放轨迹的核心代码:

private playTrack() {
  // 如果已经在播放,则停止
  if (this.playTimer) {
    this.mapController?.removeOverlay(this.polyline);
    clearInterval(this.playTimer);
    this.playTimer = undefined;
    if (this.animationTimer) {
      clearInterval(this.animationTimer);
    }
    if (this.movingMarker) {
      this.mapController?.removeOverlay(this.movingMarker);
      this.movingMarker = undefined;
    }
    this.currentPointIndex = 0;
    return;
  }

  // 创建动态位置标记
  this.movingMarker = new Marker({
    position: this.trackPoints[0],
    icon: new ImageEntity("rawfile://images/ic_run_detail_start.png"),
    isJoinCollision: SysEnum.CollisionBehavior.NOT_COLLIDE,
    located: SysEnum.Located.CENTER
  });
  this.mapController?.addOverlay(this.movingMarker);

  // 开始播放
  this.playTimer = setInterval(() => {
    this.currentPointIndex++;
    if (this.currentPointIndex >= this.trackPoints.length) {
      clearInterval(this.playTimer);
      this.playTimer = undefined;
      this.currentPointIndex = 0;
      if (this.movingMarker) {
        this.mapController?.removeOverlay(this.movingMarker);
        this.movingMarker = undefined;
      }
      return;
    }

    // 更新动态位置标记位置,使用setInterval实现平滑移动
    if (this.movingMarker && this.currentPointIndex < this.trackPoints.length - 1) {
      const currentPoint = this.trackPoints[this.currentPointIndex];
      const nextPoint = this.trackPoints[this.currentPointIndex + 1];
      let animationProgress = 0;

      // 清除之前的动画定时器
      if (this.animationTimer) {
        clearInterval(this.animationTimer);
      }

      // 创建新的动画定时器,每10ms更新一次位置
      this.animationTimer = setInterval(() => {
        animationProgress += 0.1; // 每次增加0.1的进度

        if (animationProgress >= 1) {
          clearInterval(this.animationTimer);
          this.animationTimer = undefined;
          this.movingMarker?.setPosition(new LatLng(nextPoint.lat, nextPoint.lng));
        } else {
          const interpolatedLat = currentPoint.lat + (nextPoint.lat - currentPoint.lat) * animationProgress;
          const interpolatedLng = currentPoint.lng + (nextPoint.lng - currentPoint.lng) * animationProgress;
          this.movingMarker?.setPosition(new LatLng(interpolatedLat, interpolatedLng));
        }
      }, 10); // 每10ms执行一次
    }

    // 绘制当前轨迹线段
    const currentPoints = this.trackPoints.slice(0, this.currentPointIndex + 1);
    const currentColors = PathGradientTool.getPathColors(this.record!.points.slice(0, this.currentPointIndex + 1), 100);

    if (this.polyline) {
      this.mapController?.removeOverlay(this.polyline);
      this.polyline.remove();
      this.polyline.destroy();
    }

    this.polyline = new Polyline({
      points: currentPoints,
      width: 5,
      join: SysEnum.LineJoinType.ROUND,
      cap: SysEnum.LineCapType.ROUND,
      isGradient: true,
      colorList: currentColors!
    });
    this.mapController?.addOverlay(this.polyline);

    // 更新地图中心点和旋转角度
    let bearing = 0;
    if (this.currentPointIndex < this.trackPoints.length - 1) {
      const currentPoint = this.trackPoints[this.currentPointIndex];
      const nextPoint = this.trackPoints[this.currentPointIndex + 1];
      bearing = Math.atan2(
        nextPoint.lat - currentPoint.lat,
        nextPoint.lng - currentPoint.lng
      ) * 180 / Math.PI;
      bearing = (bearing + 360) % 360;
      bearing = (360 - bearing + 90) % 360;
    }

    this.mapController?.mapStatus.setRotate(bearing).setOverlooking(90).setCenterPoint(new LatLng(this.trackPoints[this.currentPointIndex].lat, this.trackPoints[this.currentPointIndex].lng)).refresh();
  }, 100); // 每100ms移动一次
}

2.动画效果

通过定时器和线性插值实现动态轨迹的平滑移动效果。以下是动画效果的核心代码:

if (this.movingMarker && this.currentPointIndex < this.trackPoints.length - 1) {
  const currentPoint = this.trackPoints[this.currentPointIndex];
  const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  let animationProgress = 0;

  // 清除之前的动画定时器
  if (this.animationTimer) {
    clearInterval(this.animationTimer);
  }

  // 创建新的动画定时器,每10ms更新一次位置
  this.animationTimer = setInterval(() => {
    animationProgress += 0.1; // 每次增加0.1的进度

    if (animationProgress >= 1) {
      clearInterval(this.animationTimer);
      this.animationTimer = undefined;
      this.movingMarker?.setPosition(new LatLng(nextPoint.lat, nextPoint.lng));
    } else {
      const interpolatedLat = currentPoint.lat + (nextPoint.lat - currentPoint.lat) * animationProgress;
      const interpolatedLng = currentPoint.lng + (nextPoint.lng - currentPoint.lng) * animationProgress;
      this.movingMarker?.setPosition(new LatLng(interpolatedLat, interpolatedLng));
    }
  }, 10); // 每10ms执行一次
}

三、地图交互

1.地图中心点和旋转角度更新

在播放轨迹的过程中,动态更新地图的中心点和旋转角度,以确保用户始终能看到当前播放的位置。以下是更新地图中心点和旋转角度的代码:

let bearing = 0;
if (this.currentPointIndex < this.trackPoints.length - 1) {
  const currentPoint = this.trackPoints[this.currentPointIndex];
  const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  bearing = Math.atan2(
    nextPoint.lat - currentPoint.lat,
    nextPoint.lng - currentPoint.lng
  ) * 180 / Math.PI;
  bearing = (bearing + 360) % 360;
  bearing = (360 - bearing + 90) % 360;
}

this.mapController?.mapStatus.setRotate(bearing).setOverlooking(90).setCenterPoint(new LatLng(this.trackPoints[this.currentPointIndex].lat, this.trackPoints[this.currentPointIndex].lng)).refresh();

四、总结

通过上述步骤,我们成功实现了类似 Keep 的轨迹播放效果。不仅提升了用户体验,还为运动数据的可视化提供了有力支持。

全部评论

相关推荐

面的是字节的国际电商部门感觉是卷中卷了被狠狠拷打了😭面试问题:-&nbsp;解释一下ROC曲线与PR曲线的关系、ROC曲线与PR曲线的适用场景-&nbsp;介绍一下贝叶斯定理(贝叶斯公式和全概率公式)-&nbsp;考了一个概率题:已知一个随机发生器,生成&nbsp;0&nbsp;的概率为&nbsp;&nbsp;p&nbsp;,生成&nbsp;1&nbsp;的概率为&nbsp;&nbsp;1&nbsp;-&nbsp;p&nbsp;。请构造一个新的随机发生器,使其生成&nbsp;0&nbsp;和&nbsp;1&nbsp;的概率均为&nbsp;1/2。-&nbsp;(针对简历提问)了解矩阵分解吗&nbsp;MF、LFM吗-&nbsp;训练模型的时候,怎么才能知道模型是不是过拟合了?除了看训练集和测试集的准确率,还有哪些方法可以防止过拟合?比如正则化、交叉验证这些,能不能展开讲讲怎么用?-&nbsp;推荐系统里老听到CTR预估和序列推荐模型,讲讲这些模型是干啥的?比如DIN、DIEN这些CTR模型是怎么捕捉用户兴趣的?还有GRU4Rec、Caser这些序列模型是怎么处理用户行为序列的?它们各自解决了什么问题?代码题:-&nbsp;给定整数数组&nbsp;nums,求最大和的连续子数组,并返回该最大和。(最大子数组和LeetCode53)-&nbsp;手写一个二分类交叉熵bce,使用np(只把bce的公式写出来了,然后拷打怎么计算梯度,最好熟悉一下二分类梯度怎么回传的,被拷打到了这里)一面一般是组内员工,平时比较忙,这场面试约在的中午11点,所以如果能够把你的项目介绍得详细一点,就容易不让面试官问太多问题,一般我大概是2-3min自我介绍,然后再10min介绍一个项目(2-3个项目说完差不多就去一大半面试时间了),然后最后面试官不是主动型+忙着去吃饭,就会问些常见的面经,然后碰巧见过的爆率很高,然后直接吟唱。这里拷打了概率类型的问题,印象里至少有4/32次面试提到了类似的概率场景题目,建议也是稍微复习一下,至少看看基础的内容。国际电商(tiktok)据说晋升不错(同时也卷),毕竟是出海业务,但是是真的难进(听说很多清北大佬都挂了),不太懂想要招什么人(岗位名额实在太少)&nbsp;&nbsp;
查看8道真题和解析 面试问题记录
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务