一、要求1、轨迹动画流畅,慢-快-慢;2、渐变色尽量与地图渐变API的效果一致;3、拖动地图,动画消失,显示完整渐变轨迹。

二、分析1、高德地图并没有提供相应效果的API,但是可以通过经纬度坐标,转换未屏幕坐标,因此可以自定义一个View来实现轨迹动画的效果。(注意:在自定义的View上画轨迹,一定是要在地图缩放完成后执行,有对应的回调方法,API可查)

2、自定义控件这里有两种思路,可以继承自View,也可以继承SurfaceView。他们的区别相信大家都清楚,我的解决方案中使用了自定义的属性动画,所以我是通过View来实现的。欢迎大家提供SurfaceView的解决方案,共同学习。

3、渐变肯定是用Shader来进行实现,但是这里有一个误区,不能对整个运动轨迹的path设置渐变,Shader的渐变不会跟着你的轨迹走,所以只能分段设置渐变色,相信高德也是这么搞的。

4、动画效果的实现是用的属性动画,这里也有多种实现方式,最初我用了一种比较愚蠢的方案,对每一段path设置动画,通过AnimationSet进行队列展示,但是没考虑到界面渲染效率的问题,导致界面卡顿。View的渲染大家都清楚,每次invalidate都会导致界面重画,所以我的方案也很简单,通过动画进度,可以算出当前动画执行到的path,根据比例截取,进行绘制。

5、至于相关的相应事件就没什么了,实现方案很多,个人认为最简单的就是在自定义View中通过onTouchEvent处理逻辑并进行回调。

三、相关代码

1、轨迹动画相关数据的工具类

package com.lemon.running.utils;import android.graphics.LinearGradient;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.graphics.Shader;import com.lemon.running.LemonApplication;import java.util.ArrayList;/*** Created by viva on 17/7/5.*/
public class RecordPathAnimUtil {private final long MAX_ANIM_DURATION = 5 * 1000;//动画最大执行时间private final long MIN_ANIM_DURATION = 2 * 1000;private final int SCREEN_WIDTH_DEBUG = 1080;//当前调试手机的屏幕宽度,作为计算动画执行时间的标准,无实际意义private int SCREEN_WIDTH_RELEASE;//用户使用手机屏幕的实际宽度private long ANIM_DURATION = MIN_ANIM_DURATION;//动画执行总时间private final float PATH_SCREEN_LENGTH_1_KM = 2000.0f;private ArrayList<RecordPathBean> recordPathList;private PathMeasure pathMeasure;private Path totalPath;public RecordPathAnimUtil(){recordPathList = new ArrayList<>();SCREEN_WIDTH_RELEASE = ScreenUtils.getScreenWidth(LemonApplication.getContext());}public long getANIM_DURATION() {return ANIM_DURATION;}public void setANIM_DURATION(long ANIM_DURATION) {this.ANIM_DURATION = ANIM_DURATION;}public ArrayList<RecordPathBean> getRecordPathList() {return recordPathList;}/*** 创建坐标点对应的path 渐变* @param start* @param end* @param startColor* @param endColor*/public void addPath(Point start,Point end,int startColor,int endColor){if (totalPath == null){totalPath = new Path();totalPath.moveTo(start.x,start.y);totalPath.lineTo(end.x,end.y);}totalPath.lineTo(end.x,end.y);Path path = new Path();path.moveTo(start.x,start.y);path.lineTo(end.x,end.y);pathMeasure = new PathMeasure(path,false);Shader shader = new LinearGradient(start.x, start.y, end.x, end.y,new int[]{startColor,endColor},null, Shader.TileMode.CLAMP);RecordPathBean recordPathBean = new RecordPathBean(path,pathMeasure.getLength(),shader);recordPathBean.setEndPoint(end);recordPathBean.setEndColor(endColor);recordPathList.add(recordPathBean);recordPathBean.setIndex(recordPathList.size() - 1);}/*** 所有path的总长度* @return*/public float getAllPathLength(){float pathLength = 0;if (recordPathList != null){for (int i = 0,count = recordPathList.size();i < count;i++){pathLength += recordPathList.get(i).getPathLength();}}caculateAnimDuration(pathLength);return pathLength;}/*** 计算动画执行的总时长* @param pathLength*/private void caculateAnimDuration(float pathLength){float pathScreenLength1KmRelease = SCREEN_WIDTH_RELEASE * PATH_SCREEN_LENGTH_1_KM / SCREEN_WIDTH_DEBUG;float durationScale = pathLength / pathScreenLength1KmRelease;if (durationScale <= 1)return;long durationRelease = (long) (durationScale * MIN_ANIM_DURATION);if (durationRelease >= MAX_ANIM_DURATION){setANIM_DURATION(MAX_ANIM_DURATION);return;}setANIM_DURATION(durationRelease);}public Path getTotalPath() {return totalPath;}public class RecordPathBean{private Path path;//路径private Shader shader;//画笔渐变private float pathLength;private int index;private Point endPoint;private int endColor;public RecordPathBean(Path path,float pathLength,Shader shader){this.path = path;this.pathLength = pathLength;this.shader = shader;}public Path getPath() {return path;}public Shader getShader() {return shader;}public float getPathLength() {return pathLength;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}public Point getEndPoint() {return endPoint;}public void setEndPoint(Point endPoint) {this.endPoint = endPoint;}public int getEndColor() {return endColor;}public void setEndColor(int endColor) {this.endColor = endColor;}}
}

2、自定义控件

package com.lemon.running.ui.view;import android.animation.Animator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;import com.lemon.running.R;
import com.lemon.running.utils.RecordPathAnimUtil;import java.util.ArrayList;/*** Created by viva on 17/6/20.*/
public class RecordPathView extends View {private Context context;private Paint paint, iconPaint;private Path dstPath, totalPath;private PathMeasure mPathMeasure, mDstPathMeasure;private boolean isDrawRecordPath = false;private float pathLength;private Bitmap startIcon, endIcon, middleIcon;private float[] pathStartPoint = new float[2];private float[] pathEndPoint = new float[2];private float[] dstPathEndPoint = new float[2];private float value = 0;private long ANIM_DURATION;private ArrayList<RecordPathAnimUtil.RecordPathBean> recordPathList;private OnAnimEnd onAnimEnd;private int animIndex;public RecordPathView(Context context) {super(context);this.context = context;init();}public RecordPathView(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;init();}public RecordPathView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.context = context;init();}private void init() {paint = new Paint();paint.setAntiAlias(true);paint.setColor(Color.argb(0, 0, 0, 0));paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(10);iconPaint = new Paint();iconPaint.setAntiAlias(true);dstPath = new Path();startIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.outside_run_record_start_point);endIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.outside_run_record_stop_point);middleIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.speed_view_point);}public void setPath(RecordPathAnimUtil recordPathAnimUtil) {if (recordPathAnimUtil == null)return;if (!isDrawRecordPath) {pathLength = recordPathAnimUtil.getAllPathLength();ANIM_DURATION = recordPathAnimUtil.getANIM_DURATION();recordPathList = recordPathAnimUtil.getRecordPathList();totalPath = recordPathAnimUtil.getTotalPath();mPathMeasure = new PathMeasure(totalPath, false);mPathMeasure.getPosTan(0, pathStartPoint, null);//轨迹的起点mPathMeasure.getPosTan(mPathMeasure.getLength(), pathEndPoint, null);//轨迹的终点if (recordPathList == null || recordPathList.size() == 0)return;startPathAnim();isDrawRecordPath = true;}}public void setOnAnimEnd(OnAnimEnd onAnimEnd) {this.onAnimEnd = onAnimEnd;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (recordPathList == null || recordPathList.size() == 0)return;if (animIndex > 0){for (int i = 0; i < animIndex; i++) {RecordPathAnimUtil.RecordPathBean recordPathBean = recordPathList.get(i);paint.setColor(recordPathBean.getEndColor());paint.setShader(recordPathBean.getShader());paint.setStrokeWidth(10);paint.setStyle(Paint.Style.STROKE);canvas.drawPath(recordPathBean.getPath(), paint);paint.setShader(null);paint.setStrokeWidth(1);paint.setStyle(Paint.Style.FILL_AND_STROKE);canvas.drawCircle(recordPathBean.getEndPoint().x, recordPathBean.getEndPoint().y, 5, paint);}}paint.setStyle(Paint.Style.STROKE);paint.setShader(recordPathList.get(animIndex).getShader());paint.setStrokeWidth(10);canvas.drawPath(dstPath, paint);canvas.drawBitmap(startIcon, pathStartPoint[0] - startIcon.getWidth() / 2, pathStartPoint[1] - startIcon.getHeight() / 2, iconPaint);if (value >= 1) {canvas.drawBitmap(endIcon, pathEndPoint[0] - endIcon.getWidth() / 2, pathEndPoint[1] - endIcon.getHeight() / 2, iconPaint);} else {canvas.drawBitmap(middleIcon, dstPathEndPoint[0] - middleIcon.getWidth() / 2, dstPathEndPoint[1] - middleIcon.getHeight() / 2, iconPaint);}}private void caculateAnimPathData(){float length = value * pathLength;float caculateLength = 0;float offsetLength = 0;for (int i = 0,count = recordPathList.size();i < count;i++){caculateLength += recordPathList.get(i).getPathLength();if (caculateLength > length){animIndex = i;offsetLength = caculateLength - length;break;}}dstPath.reset();PathMeasure pathMeasure = new PathMeasure(recordPathList.get(animIndex).getPath(),false);pathMeasure.getSegment(0, recordPathList.get(animIndex).getPathLength() - offsetLength, dstPath, true);mDstPathMeasure = new PathMeasure(dstPath, false);mDstPathMeasure.getPosTan(mDstPathMeasure.getLength(), dstPathEndPoint, null);}private void startPathAnim() {ValueAnimator animator = ValueAnimator.ofObject(new DstPathEvaluator(), 0, mPathMeasure.getLength());animator.setDuration(ANIM_DURATION);animator.setInterpolator(new AccelerateDecelerateInterpolator());animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {value = (float) animation.getAnimatedValue();caculateAnimPathData();invalidate();}});animator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {if (onAnimEnd != null)onAnimEnd.animEndCallback();}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});animator.start();}class DstPathEvaluator implements TypeEvaluator {@Overridepublic Object evaluate(float fraction, Object startValue, Object endValue) {return fraction;}}public interface OnAnimEnd {void animEndCallback();}float x, y;@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:x = event.getX();y = event.getY();break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_MOVE:case MotionEvent.ACTION_CANCEL:if (Math.abs(event.getX() - x) > 0 || Math.abs(event.getY() - y) > 0) {if (onAnimEnd != null)onAnimEnd.animEndCallback();}break;default:break;}return true;}
}

注:本文只是提供一种实现方案,其实针对于跑步路径过长的情况,这样处理还是会有跳帧的问题。毕竟代码是死的,人是活的,针对这种情况也有多种优化方案,需要与产品、设计的要求找到一个平衡。
我们自己测试的情况,当小段path的数量大概到达2000的时候,就会跳帧。那么在渲染的时候就需要两种解决方案,可以从渐变上考虑,可以从绘制方式上考虑等。

优化方案请看下一篇文章:
http://www.jianshu.com/p/996f2cfeed29


作者:BaseCoder链接:https://www.jianshu.com/p/cedfe72be146來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

地图跑步轨迹回放动画实现相关推荐

  1. 高德地图3D轨迹回放 + 视野跟随功能

    高德地图3D轨迹回放 + 视野跟随功能 概述 代码实现步骤 完整代码 (需要添加一个2.0的key) 概述 若有帮助到你,麻烦点一波关注,博主会持续推出Echarts,D3,地图,Three.js方面 ...

  2. android 基于高德地图的轨迹回放

    android 基于高德地图的轨迹回放 前段时间公司项目有一个需求,就是需要看到设备上传之后的轨迹路线,并且可以实现回放的整个过程,功能包括路线回放.地图位置插点.回放之后的轨迹标记颜色.回放加速等功 ...

  3. vue使用高德地图制作小车轨迹回放动画简单案例

    首先在根目录public中的index.html引入高德地图 <script type="text/javascript" src="https://webapi. ...

  4. js室内地图开发_如何使用JS来开发室内三维地图的轨迹回放功能

    在制作完成室内三维地图的功能后,最经常有的需求就是如何做人员的轨迹回放,一般流程都是从数据库中查询轨迹坐标后,经过后台查询接口返回给前端,接下来的事情都交给JS来完成. 如果想做好一个性能好的轨迹回放 ...

  5. 百度地图的轨迹回放和实时监控

    最近写了两个页面,是跟百度地图有关的,本来以为是用的鹰眼的轨迹,才发现没那么麻烦,直接用的是百度地图自带的api. 1 轨迹回放 <template><div class=" ...

  6. 百度地图实现轨迹回放(显示到达时间)

    使用了百度路书 直接上代码,添加秘钥直接运行 <!DOCTYPE html> <html> <head><meta http-equiv="Cont ...

  7. H5移动端 高德地图 巡查轨迹回放 2.0版

    注意是HTTPS还是HTTP index.html引入 <script type="text/javascript" src="http://webapi.amap ...

  8. bing地图车辆轨迹回放代码

    1导入js <script type="text/javascript"src="http://ajax.googleapis.com/ajax/libs/jque ...

  9. 高德地图轨迹回放功能

    一.介绍        在项目过程中,需要对自己设备产品输出的定位信息进行验证.通过路跑测试获取到了一组经纬度数据.这时需要验证这组数据是否是实际路跑测试的轨迹,就用到了高德地图的轨迹回放功能.下面将 ...

最新文章

  1. WinCvs 操作参考手册
  2. 记一次项目中由id类型引起的bug
  3. 【C++】欧几里德算法快速求最大公约数
  4. 【Spring MVC】 maven pom.xml 错误: Cannot upgrade/downgrade to Dynamic Web Module 3.0 facet.
  5. No Authorization to generate extension field
  6. ajax里怎么添加跳转地址,Ajax中window.location.href无法跳转的解决办法
  7. ASP.NET Core的身份认证框架IdentityServer4--入门【转】
  8. 内存分布malloc/calloc/realloc/free/new/delete、内存泄露、String模板、浅拷贝与深拷贝以及模拟string类的实现
  9. 退休的姐妹们,你们还打工吗?
  10. 你要的六级成绩批量查询,它来啦......
  11. sum-ftp-w.sh
  12. 将solidity智能合约打包成Java代码
  13. google Inception v1 - v4 papers 发展历程
  14. 网站地图在线生成html,如何制作网站地图(sitemap.html和sitemap.xml)?
  15. 数据库锁机制和CAS概念
  16. 漫画:一场大会,秀出了中国人自己的操作系统!
  17. 计算机电脑故障,电脑常见故障解决方法
  18. 初学Unity3D——材质球
  19. 凸集 凸函数 判定凸函数
  20. NEFU 1266 快乐的雨季 (线段树)

热门文章

  1. 热议:老公今年已经34周岁想读博,以后做科研,怎么办?
  2. JavaScript原型详解
  3. windows 新版skia编译 版本号m37_2062(2018.1.1)
  4. Deepin安装SSH服务器
  5. 面试智力题:海盗分珍珠
  6. dialog弹出时,点击dialog之外的地方时,dialog不消失。
  7. Facebook广告数据
  8. 微星z370安装linux系统,华硕z370主板装win10系统及bios设置(uefi+gpt方式安装)
  9. 世界杯太精彩了,带大家用Python做个足球游戏,边玩游戏边看比赛
  10. php parse url 反向,php parse_url()函数