源码地址:https://github.com/zyktojo/AmapSmoothMarker
欢迎Star~~
开端:
打开滴滴打车APP,会发现地图上的车辆显示的十分形象,车辆会在路上平滑的加减速,转向停车~
有种即时战略的感觉,看起来挺有趣:
为了研究这个效果如何实现,我开始了探索之旅:
一.探索阶段:
1.旧版平滑移动
首先找到的是百度“高德地图平滑移动”结果里的这个文:
http://lbs.amap.com/smart/transportation/skill/move/
实现方式是开启子线程,不断执行绘制-销毁图标marker方法来实现看起来在不断向前移动的动画:
于是下载了这个DEMO,跑起来,发现确实移动效果不错
但是!!
问题是这个实现方式很难支持多点同时移动
经过实践发现使用这个方法同时操纵多marker时,很大概率只能成功移动最后操作的那个marker,而之前的所有marker都一动不动
努力改造两天之后,依然没有成功保证所有该动的marker都动起来,因此决定另寻他路
2.新版移动
运气还不错,在高德官方文档里找到了17年刚刚发布的新文档:
http://lbs.amap.com/api/android-sdk/guide/draw-on-map/smooth-move
简单浏览了一下这个新文档和DEMO,发现这个实现方式明显封装更良好,也更能保证线程和内存安全
二.开始实现
1.下载最新版Jar包:4.1.3
要想实现这个,必须使用最新版的高德地图SDK,即4.1.3以上版本
在Gradle修改版本,刷新Gradle,搞定
2.会移动的Mark类:SmoothMoveMarker
先看看官方实现的逻辑代码:
// 获取轨迹坐标点
List<LatLng> points = readLatLngs();
LatLngBounds bounds = new LatLngBounds(points.get(0), points.get(points.size() - 2));
mAMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
SmoothMoveMarker smoothMarker = new SmoothMoveMarker(mAMap);
// 设置滑动的图标
smoothMarker.setDescriptor(BitmapDescriptorFactory.fromResource(R.drawable.icon_car));
LatLng drivePoint = points.get(0);
Pair<Integer, LatLng> pair = SpatialRelationUtil.calShortestDistancePoint(points, drivePoint);
points.set(pair.first, drivePoint);
List<LatLng> subList = points.subList(pair.first, points.size());
// 设置滑动的轨迹左边点
smoothMarker.setPoints(subList);
// 设置滑动的总时间
smoothMarker.setTotalDuration(40);
// 开始滑动
smoothMarker.startSmoothMove();
发现高德新增了一个支持移动的Marker类:SmoothMoveMarker
只要放入轨迹,设定好滑动时间,他就会愉快的滑动起来啦~
3.初步实现多Marker移动
阅读高德demo源码,发现他封装了两个方法来实现移动,一个是移动,另一个是读取路线坐标值:
//平滑移动 public void movePoint() { // 获取轨迹坐标点 List<LatLng> points = readLatLngs(); LatLngBounds bounds = new LatLngBounds(points.get(0), points.get(points.size() - 2)); aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); SmoothMoveMarker smoothMarker = new SmoothMoveMarker(aMap); // 设置滑动的图标 smoothMarker.setDescriptor(BitmapDescriptorFactory.fromResource(R.drawable.car_up)); LatLng drivePoint = points.get(0); Pair<Integer, LatLng> pair = SpatialRelationUtil.calShortestDistancePoint(points, drivePoint); points.set(pair.first, drivePoint); List<LatLng> subList = points.subList(pair.first, points.size()); // 设置滑动的轨迹左边点 smoothMarker.setPoints(subList); // 设置滑动的总时间 smoothMarker.setTotalDuration(10); // 开始滑动 smoothMarker.startSmoothMove(); } //获取路线 private List<LatLng> readLatLngs() { List<LatLng> points = new ArrayList<LatLng>(); for (int i = 0; i < coords.length; i += 2) { points.add(new LatLng(coords[i + 1], coords[i])); } return points; }
为了观察多点同时移动,决定把这个方法重复实现两次,两条路线不一样
那怎么设定不同的路线呢?
readLatLngs():这个方法读取了名为coords的集合
那么我们就先设定一个deuble[]集合
把路线经过点的各个坐标值放进去
第二次运行的时候再改变这个集合就OK了~
如下例,地图中心点是天安门,两辆车会从天安门西边,一个向南一个向北移动:
double[] newoords = {116.380729, 39.906443, 116.330776, 39.868508}; coords = newoords; movePoint(); double[] newoords2 = {116.319618, 39.929614, 116.437377, 39.906443}; coords = newoords2; movePoint();经过试验,可以良好的支持多点同时移动
——————————————基础实现部分到这里为止,接下来是具体业务开发相关————————————————
4.加入路线,刷新
搞定了基础实现,接下来就要考虑具体场景了
1.数据源:
由于司机端的数据源信息更新慢(每10秒报一轮GPS),方向信息缺乏(静止时没有方向数据)
考虑到这些原因,我决定把平滑移动的时间改为10秒:
// 设置滑动的总时间 smoothMarker.setTotalDuration(10);
查询附近车辆的接口,也改为每十秒钟循环一次,以达到与司机端发送信息的频率一致
carsHandler.postDelayed(this, 10000);
2.对照车辆ID
如何判断新得到的那些车,有哪几辆是与上次搜索时一样的呢?
首先,必须有车辆ID
看看接口文档,接口数据里除了经纬度之外还带了个car_id,果断拿来对照一下
具体实现时要考虑两种情况:
1.如果car_id相同:平滑移动marker
2.如果car_id不同:展示静态marker
5.问题:如何清除旧marker?
很简单,每次成功请求到新的附近车辆数据时,把AMap对象clear()一下就行咯
缺点是会有瞬间的地图闪动,那一瞬间地图上所有附加的图标点全部清空,不过时间很短不太容易察觉到
效果展示:
三.优化
1.第一次加载时的角度
在写完上述功能后,发现一个问题:
车辆第一次加载在地图上时,marker的角度全都是默认角度:0度,头朝上
这可不太友好,于是问了问司机端,能不能获取一下即时的车辆角度?
回答是:动的时候可以,静止之后不能
于是又问了服务端,能不能做一个“记录车辆最后一次报告的角度”功能
这个当然可以~
于是完美解决了第一次加载车辆marker时的角度问题
2.地图挪动时,太灵敏
解决了角度问题,又发现一个新的问题,就是“地图中心点改变时,如何即时刷新附近车辆?”
高德地图的API给出的监听方法是:
onCameraChangeFinish()
用来在地图被挪动后执行一些方法
之前一直把获取附近车辆的接口方法写在这里,因而造成了:
即使地图只挪了1米远,同样会发出一遍获取附近车辆的请求
这样造成的坏处是:
1.增加服务器负担
2.影响正常marker动画展示,因为前后两次请求如果小于10秒,那么请求到的坐标值是一样的,这样的话车辆就不会移动了
一个简单的解决方案是:
把“请求附近车辆”的方法放进界面onCreate()或者onResume()方法里,用Hnadler继承runnable接口的方式写一个循环执行的线程
这样解决了地图挪动太灵敏的问题,不过也造成了地图挪动后,新位置附近的车加载过慢的新问题
要解决这个也很简单,刚才说到的onCameraChangeFinish()方法,可以在这个方法里销毁一下刚才的Handler并且重新post()一遍就行啦~
哎不对,这岂不是跟刚开始的时候一样了嘛?
淡定,这一步还没做完,我们可以对比一下地图前后中心点的距离,当它大于某个长度后才进行搜索嘛~
这样每次地图挪动之后都会重新执行一下“加载附近车辆”的方法,同时又不用担心加载过于灵敏或者线程重复执行的问题啦
3.渐变展现
在快要完成这项功能的最后,我发现滴滴的车辆展示其实不是立马展示上去的,而是有一个渐隐动画
我决定也做一个类似的效果
用到了经典的安卓动画文件夹:anim
里面可以设定透明度:alpha:
还可以设定展示的时间during:
设定好参数之后呢,在java文件里加载也很方便,
让指定的marker对象:.startAnimation(animationutils.loadanimation(R.anim.xxx_xx));
大功告成~
效果展示: