仿滴滴打車的平滑移動附近車輛效果實現全流程,基於高德地圖

源碼地址: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));

大功告成~


效果展示:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章