喜爱户外运动的朋友一般都应该使用过运动APP(keep, 咕咚,悦跑圈,国外的Strava等)的一项功用,便是运动轨道视频共享,共享到朋友圈或是运动群的圈子里。笔者自身平常也是喜爱户外跑、骑行、爬山等户外运动,也跑过半马、全马,疫情原因之前报的杭州的全马也延期了好几次了。回归正题,本文笔者根据自己的思维完成运动轨道回放的一套算法战略,完成自身是根据Mapbox地图的,可是其实能够套用在任何地图都能够完成,根本能够脱离地图SDK的API。Mapbox 10 版本之后的官方给出的Demo里已经有类似轨道回放的Case了,可是深度地依靠地图SDK自身的API,倘若在高德上完成很难能够迁移的。

这儿先看下gif动图的效果,这是我在奥森跑的10KM的一个轨道:

运动APP视频轨迹回放分享实现

整个的完成包含了轨道的回放,视频的录制,然后视频的录制这块不再笔者这篇文章的介绍的领域内。所以这儿首要介绍轨道的回放,这个回放过程其实也是包含了大约10多种动画在里面的,辅助信息间隔的文字跳转动画;间隔下面配速、运动时间等的flap in 及 out的动画;播映button,底部button的突变Visibility; 地图的缩放以及视觉视点的改变等;以上的这些也不做讨论。首要介绍轨道回放、整公里点的显现(起始、完毕), 回放过程中窗口操控等,作为首要的解说领域。

首先介绍笔者最开端的一种完成,假定以上轨道List 有一百个点,每相邻的两个点做Animation之后,在AnimationEnd的Listener里开起间隔下一个点的Animation,直到所有点完毕,这儿有个问题每次的运动轨道的点的数量不一样,所以开起Animation的次数也不一样,整个轨道回放的时间等于所有的Animation履行的时间和,每次动画发动需要损耗20~30ms。倘若要共享到微信朋友圈,视频的时间是限制的,但之前的那种方式时间上明显不可控,每次动画发动的损耗累加导致视频播映不完。

紧接着换成AnimationSet, 将各个线段Animation的动画放入Set里,然后playSequentially履行,同样存在上面的问题。假定只履行一次动画,那么这次动画start的损耗在整个视频播映上时长上的占比就能够忽略不计了,那怎么才能将整个List的回放在一个Animation下履行完呢?假定轨道仅仅一个一般的 Path,那么我们就能够根据Path的 length一个特点动画了,当转化到地图运动轨道上去时,又怎么去完成呢?

根据Path Length的特点动画

  1. 核算List对应的Path
  2. 经过PathMeasure获取 Path 的 Length
  3. 对Path做 Length的特点动画

这儿有两套Point体系,一个是View的Path对应的Points, 然后便是Map上的List对应的Points,运动轨道原始数据是Map上的List 点,上面的第一步便是将Map上的Points 转成屏幕Pixel对应的点并生成Path; 第二部经过PathMeasure 核算Path的Length; 最终在Path Length上做特点动画,然而这儿并非将特点动画中每次突变的值(这儿对应的是View的Point点)制作成View对应的Path,而是将突变中的点又经过Map的SDK转成地图Location点,制作地图轨道。这儿一共做了两道转化,中心仅仅凭借View的Path做了一个依仗Length特点做的一个动画。因为根本上每种地图SDK都有Pixel 跟Location Point点互相transform的API,所以这个能够直接迁移到其它地图上,例如高德地图等。

下面具体看下代码,先将Location 转成View的Point体系,这儿保存了总的一个Path,以及List 中两两相邻点对应的分段Path的一个list.

  • 生成Path:

运动APP视频轨迹回放分享实现

其中用到 Mapbox地图API Location 点转View的PointF 接口API toScreenLocation(LatLng latlng), 这儿生成List, 然后核算得到Path.

运动APP视频轨迹回放分享实现

  • 根据Length做特点动画:

运动APP视频轨迹回放分享实现

首先创立特点动画的 Instance:

ValueAnimator.ofObject(new DstPathEvaluator(), 0, mPathMeasure.getLength());

将每次突变的值经过 calculateAnimPathData(value) 核算后存入到 以下的四个变量中,这儿除了Length的突变值,还附带有视点的一个二元组值。

dstPathEndPoint[0] = 0;//x坐标
dstPathEndPoint[1] = 0;//y坐标
dstPathTan[0] = 0;//视点值
dstPathTan[1] = 0;//视点值

然后将dstPathEndPoint 的值转成Mapbox的 Location的 Latlng 经纬度点,

PointF lastPoint = new PointF(dstPathEndPoint[0], dstPathEndPoint[1]);
LatLng lastLatLng = mapboxMap.getProjection().fromScreenLocation(lastPoint);
Point point = Point.fromLngLat(lastLatLng.getLongitude(), lastLatLng.getLatitude());

过滤掉一些动画过程中或许产生的反常点,最终加入到Mapbox的轨道制作的Layer中形成轨道的一个突变:

Location curLocation = mLocationList.get(animIndex);
float degrees = MapBoxPathUtil.getRotate(curLocation, point);
if (animIndex < 5 || Math.abs(degrees - curRotate) < 5) {//排除反常点
    setMarkerRecord(point);
}

setMarkerRecord(point) 方法调用加入到 Map 轨道的制作Layer中

运动APP视频轨迹回放分享实现

动画过程中,当加入到Path中的点超过必定占比时,做了一个窗口显现的动画,窗口List跟整个List的一个核算:

//这儿能够取后半段的数据,滑动窗口,保持 moveCamera 的窗口值不变。
int moveSize = passedPointList.size();
List<LatLng> windowPassList = passedPointList.subList(moveSize - windowLength, moveSize);

接下来看整公里点的制作,看之前先看下上面的calculateAnimPathData()方法的逻辑

运动APP视频轨迹回放分享实现

如上,length为当时Path走过的间隔,假定轨道一共100点,当时走到 49 ~ 50 点之间,那么calculateLength便是0到50这个点的Path的长度,它是大于length的,offsetLength = calculateLength – length; 记载的是 当时点到50号点的一个长度offsetLength,animIndex值当时值对应50,recordPathList为一开端提到的跟核算总Path时一个分段Path的List, 获取到49 ~ 50 这个Path对应的一个model.

RecordPathBean recordPathBean = recordPathList.get(animIndex);

获得Path(49 ~ 50) 的长度减去 当时点到 50的Path(cur ~ 50)的到 Path(49 ~ cur) 的长度

float stopD = (float) (pathMeasure.getLength() - offsetLengthCur);

然后最终经过PathMeasure的 getPosTan 获得dstPathEndPoint以及dstPathTan数据。

pathMeasure.getSegment(0, stopD, dstPath, false);
mDstPathMeasure = new PathMeasure(dstPath, false);
//这儿有个参数 tan
mDstPathMeasure.getPosTan(mDstPathMeasure.getLength(), dstPathEndPoint, dstPathTan);
  • 整公里点的制作

原始数据中的List的Location中存储了一个字段kilometer, 当某个Location是整公里点时该字段就有对应的值,每次Path特点突变时,上面的逻辑里记载了lastAnimIndex, animIndex。当 animIndex > lastAnimIndex时, 上面的calculateAnimPathData() 方法里剖析animIndex有或许还没走到,所以在animIndex > lastAnimIndex时lastAnimIndex肯定走到了。

运动APP视频轨迹回放分享实现

当lastAnimIndex对应的点是 整公里时,做一个呼应的特点动画。

至此,运动轨道回放的一个动画履行逻辑剖析完了,如文章开端所说,整个过程中其实还包含了好多种其它的动画,处理它们播映的一个时序问题,怎么编列完成等等也是一个难点。另外还便是轨道播映时的一个Camera的一个视觉跟踪的效果没有完成,这个用地图自身的Camera 的API是一种完成,可是怎么跟上面的这些结合到一块;然后便是自行经过核算视点偏移,累计到必定的旋转视点时,搬运地图的指南针;以上是笔者想到的方案,以上有核算视点的,但需要找准那个累计的视点值,然后大量实际数据适配。

最终,有需要了解轨道回放功用其它完成的,可留言或私信笔者进行一同探讨。