源碼地址: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));
大功告成~
效果展示: