前言

在运动类应用中,轨迹播放效果是提升用户体验的关键功能之一。它不仅能直观展示用户的运动路线,还能通过动态效果增强运动的趣味性。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 的轨迹播放效果。不仅提升了用户体验,还为运动数据的可视化提供了有力支持。

鸿蒙运动开发实战:打造 Keep 式轨迹播放效果的更多相关文章

  1. web开发实战--弹出式富文本编辑器的实现思路和踩过的坑

    前言: 和弟弟合作, 一起整了个智慧屋的小web站点, 里面包含了很多经典的智力和推理题. 其实该站点从技术层面来分析的话, 也算一个信息发布站点. 因此在该网站的后台运营中, 富文本的编辑器显得尤为 ...

  2. Android开发实战之底部Dialog弹出效果

    在Android开发中,有很多情况下我们需要使用到对话框,遗憾的是,安卓自带的对话框样式不能满足我们实际的需要,所以往往需要我们自定义对话框,具体做法:写一个对话框继承自Dialog实现他的一个构造方 ...

  3. 最全华为鸿蒙 HarmonyOS 开发资料汇总

    开发 本示例基于 OpenHarmony 下的 JavaScript UI 框架,进行项目目录解读,JS FA.常用和自定义组件.用户交互.JS 动画的实现,通过本示例可以基本了解和学习到 JavaS ...

  4. Web前端开发实战6:CSS实现导航菜单结合二级下拉式菜单的简单变换

    前面几篇博文都在讲导航菜单和二级下拉式菜单,事实上有非常多方法都能够实现的.详细的情况还要视情况而定. 在后面学习到jQuery框架之后,会有更丰富的动画效果.因为在学习Ajax和jQuery的初步阶 ...

  5. 《微信小程序项目开发实战:用WePY、mpvue、Taro打造高效的小程序》(笔记1)WePY开发环境的安装

    WePY的安装或更新都通过npm进行,全局安装或更新WePY命令行工具,使用以下命令: npm install wepy-cli -g 稍等片刻,成功安装后,即可创建WePY项目. 注意:如果npm安 ...

  6. 微信小程序项目开发实战:用WePY、mpvue、Taro打造高效的小程序》(笔记4)支持React.js语法的Taro框架

    Taro本身实现的情况类似于mpvue,mpvue的未来展望中也包含了支付宝小程序,现在的版本中,也可以使用不同的构建命令来构建出百度小程序的支持,如第10章所示,但是现在Taro先于mpvue实现了 ...

  7. 《Visual Basic开发实战1200例》包括第I卷、第II卷共计1200个例子,本书是第I卷,共计600个例子。

    本书以开发人员在项目开发中经常遇到的问题和必须掌握的技术为中心,介绍了应用Visual Basic进行程序开发各个方面的知识和技巧.主要包括基础知识.窗体界面设计.控件应用等.全书分6篇20章,共计6 ...

  8. HTML5移动Web开发实战 PDF扫描版​

    <HTML5移动Web开发实战>提供了应对这一挑战的解决方案.通过阅读本书,你将了解如何有效地利用最新的HTML5的那些针对移动网站的功能,横跨多个移动平台.全书共分10章,从移动Web. ...

  9. iPhone与iPad开发实战读书笔记

    iPhone开发一些读书笔记 手机应用分类1.教育工具2.生活工具3.社交应用4.定位工具5.游戏6.报纸和杂志的阅读器7.移动办公应用8.财经工具9.手机购物应用10.风景区相关应用11.旅游相关的 ...

  10. Swoole入门到实战 打造高性能 赛事直播平台(完整版)

    Thinkphp+Swoole入门到实战打造高性能赛事直播平台 第1章 课程介绍 欢迎大家来到swoole的课程!本章主要是介绍了swoole的一些特性,以及使用场景,并且分享了swoole在其他公司 ...

随机推荐

  1. Scala样例类及底层实现伴生对象

    package com.wyh.day01 /** * 样例类的使用 * 1.使用case修饰类 * 2.不需要写构造方法,getter,setter方法,toString方法 * 3.直接通过对象名 ...

  2. Vulkan环境配置 | vscode+msvc 解决方案

    Vulkan环境配置 | vscode+msvc 解决方案 前言 如果作为Windows 11侠的你是一个vscode爱好者,凑巧电脑上还安装有Visual Studio,这个时候你突然想配置一个Vu ...

  3. 解密注意力机制:为什么Flash Attention这么屌?

    背景回顾:什么是大语言模型(LLM)? 在进入注意力机制的细节之前,我们先了解一下什么是大语言模型(LLM).简单来说,LLM是一种通过深度学习技术训练的大规模神经网络模型,用于处理和生成自然语言.L ...

  4. 【vscode】vscode配置汇编环境

    [vscode]vscode配置汇编环境 前言 ‍ 因为近来个人的课程涉及到汇编语言,加上个人目前是个vscode的重度使用者,所以,要捣鼓一下汇编的配置. 自然,有很多博客写过如何配置,但是每个人在 ...

  5. websocket 后台新订单通知 —— Laravel 5.8 workman PHPSocket.IO教程

    websocket 后台新订单通知 -- Laravel 5.8 Laravel-echo-server教程 PHPSocket.IO,PHP跨平台实时通讯框架 PHPSocket.IO是PHP版本的 ...

  6. 【高德地图】离线地图jsapi2.0 插件引用

    当引用一个插件,但是这个插件没有时会报错: Uncaught TypeError: AMap.ControlBar is not a constructor 这里引用了罗盘这个插件 这时候需要翻看高德 ...

  7. Python 加上颜色进行输出

    博客地址:https://www.cnblogs.com/zylyehuo/ print(f"\033[42m文本内容\033[0m")

  8. .NET 中的 Swagger 文档排序设置

    Swagger的API默认排序往往达不到效果,甚至设置了Action排序也没有作用.这里直接给出代码,关键在于 IDocumentFilter 实现. 注意 DocumentFilter 注册要放在尾 ...

  9. 大模型 Token 究竟是啥:图解大模型Token

    前几天,一个朋友问我:"大模型中的 Token 究竟是什么?" 这确实是一个很有代表性的问题.许多人听说过 Token 这个概念,但未必真正理解它的作用和意义.思考之后,我决定写篇 ...

  10. nodejs集群

    nodejs集群 单个 Node.js 实例运行在单个线程中. 为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务. 集群中的Master 现在让我们详细了解Master的职责 ...