利用百度地圖實現支付寶 “到位” 功能(地圖模式)

戀貓de小郭發表於2016-11-29

Hello,大家好,郭老司機又來話癆啦((/- -)/。作為一個芝麻信用分762的窮苦青年,本想著,終於可以在支付寶上,光明正大的勾搭坦誠相見的妹子們,誰知道被和諧了,網際網路套路啊(ノಠ益ಠ)ノ彡┻━┻。

 
 算了,還是聊今天我們要說的,支付寶的“到位”功能。新版支付寶上方第四個tab,傳聞丈母孃足不出戶,同城招女婿的神兵利器。反正上面那些一塊錢看看花、逛逛街、喝喝酒的服務好單純好不做作<( ̄ˇ ̄)/。

我是DEMO: github.com/CarGuo/LbsM… 用力戳♂起來。

利用百度地圖實現支付寶 “到位” 功能(地圖模式)

 到位的主要特色是地圖LBS功能,搜尋周邊的服務和幫忙,既然是地圖,我們就站在百度的肩膀來開車吧:

  • 百度地圖的基本功能:地圖,Marker,聚合。
  • 百度地圖的LBS功能。
  • 圖片Icon的下載與Marker更新。

1、基礎地圖功能

 
 首先你得有個KEY,在百度地圖API上註冊為開發者後,你就可以建立一個應用,記得選Android型別,因為預設是伺服器型別。

 之後在AndroidManifest上加上下面這些(網路、定位許可權的就不需要列出了吧),最後在APPlication中初始化SDKInitializer.initialize(this);,這樣你的的MapView就可以跑起來啦!

<service
    android:name="com.baidu.location.f"
    android:enabled="true"
    android:process=":remote" />

<meta-data
    android:name="api_key"
    android:value="CirU5l4MGVbE59tOduYDCO6TDX27CPzV" />

<meta-data
    android:name="com.baidu.lbsapi.API_KEY"
    android:value="CirU5l4MGVbE59tOduYDCO6TDX27CPzV" />複製程式碼

2、初始化地圖

 
 雖然用的是MapView,但是實際上操作的,是MapView裡面的BaiduMap

 下方長♂程式碼走起,流程是:比例尺;不要傾角;不要旋轉;設定最大和最小的縮放層級;初始化聚合管理器(後面都是他的事情);Marker(地圖上對應的item)管理器;設定顯示位置的圖示; 初始化我們需要的搜尋Model(經緯度,半徑,表id)用與儲存搜尋狀態。是不是很簡單,哇塞,我有一個地圖了。


mBaiduMap = mBaiduMapView.getMap();
// 比例尺控制元件
mBaiduMapView.showScaleControl(true);
// 縮放控制元件
mBaiduMapView.showZoomControls(false);
// 百度地圖LoGo -> 正式版切記不能這麼做,本人只是覺得logo醜了
mBaiduMapView.removeViewAt(1);
//不傾斜
mBaiduMap.getUiSettings().setOverlookingGesturesEnabled(false);
//不旋轉
mBaiduMap.getUiSettings().setRotateGesturesEnabled(false);
//設定縮放層級
mBaiduMap.setMaxAndMinZoomLevel(19, 12);
//圖示管理器
mMarkerManager = new MarkerManager(mBaiduMap);
//聚合與渲染管理器
mClusterManager = new ClusterManager<>(this, mBaiduMap, mMarkerManager);

mBaiduMap.setOnMapStatusChangeListener(mClusterManager);

mBaiduMap.setOnMapLoadedCallback(this);

mBaiduMap.setMyLocationEnabled(true);

Bitmap bitmap = CommonUtil.getImageFromAssetsFile(DemoApplication.getApplication(), "current_location.png");

//調整位置圖片,類似百度地圖的小藍點
float scale = 0.80f;
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
mCLBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

bitmap.recycle();

BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(mCLBitmap);
MyLocationConfiguration myLocationConfiguration = new MyLocationConfiguration(MyLocationConfiguration.LocationMode.NORMAL, false, bitmapDescriptor);
mBaiduMap.setMyLocationConfigeration(myLocationConfiguration);

//顯示位置圖示的builder
MyLocationData locData = new MyLocationData.Builder()
        .accuracy(0)
        .direction(0).latitude(llat)
        .longitude(llng).build();

//顯示位置圖示-珠海
mBaiduMap.setMyLocationData(locData);

//顯示等級-轉換:初始化為mDefaultRadius半徑的層級用於顯示
float level = LocationLevelUtils.returnCurZoom(mDefaultRadius);

//當前地圖狀態
mCurrentMapStatus = new MapStatus.Builder().target(new LatLng(llat, llng)).zoom(level).build();

mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(mCurrentMapStatus));

//初始化資料搜尋model
mSearchModel = new SearchModel();
mSearchModel.setGps(llng + "," + llat);
mSearchModel.setRadius(mDefaultRadius);
mSearchModel.setLevel(level);
mSearchModel.setTableId(DemoApplication.TABLE_ID());複製程式碼

 

2、修改百度地圖demo原始碼

 
 初始化好地圖,那麼我們需要的是,在地圖上顯示圖示,並且將臨近的點聚合起來。百度地圖的DEMO已經實現了這一點,但是還不夠我們的需求,這是時候我們要修改Demo下,clusterutil路徑下的類,針對聚合與Marker的顯示做自定義處理。

1)、ClusterItem

 這個介面代表著地圖上一個item,但是的百度Demo還不夠,所以在裡面加入新介面,用於顯示我們的自定圖示,然後實現ClusterBaiduItem繼承ClusterItem,將需要顯示的經緯度和圖示資訊,儲存在Item裡。

/**
 * 網路的單個marker的例項
 */
BitmapDescriptor getUrlMarkerIconBitmapDescriptor(boolean select);

/**
 * 網路的單個marker的icon路徑
 */
String getUrlLocalMarkerIconPath();

/**
 * 網路的單個聚合的icon路徑
 */
String getUrlClusterIconPath();複製程式碼

2)、ClusterManager

 這是聚合marker的管理器,內部有渲染類,將ICON渲染到地圖上,同時也包含了地圖狀態變化的介面,這裡我們把地圖的狀態變化介面回撥出來,方便我們監聽地圖的移動和縮放。

 同時對渲染類DefaultClusterRenderer也增加get介面,因為後面我們需要,在外部動態改變Marker的圖示。地圖上渲染出來的Marker在Render渲染類中,會以ClusterItem為KEY,快取在MAP中。

//add myself 修改了地圖狀態變化的回撥
public BaiduMap.OnMapStatusChangeListener onMapStatusChangeListener;

/**
 * add myself
 */
public DefaultClusterRenderer<T> getDefaultClusterRenderer() {
    return (DefaultClusterRenderer) mRenderer;
}複製程式碼

3)、NonHierarchicalDistanceBasedAlgorithm

 這個類主要關注MAX_DISTANCE_AT_ZOOM,它代表著多遠的距離可以聚合,這裡個人修改為200,反正我就不喜歡堆在一起╮(╯_╰)╭。

4)、DefaultClusterRenderer

 這是大頭,很大的頭,預設渲染類,當然你可以自己實現,但是秉承著快速(懶)開發的原則,我是直接在上面修改的:

  • 修改 makeClusterBackground方法,把其中的聚合背景效果,替換為我們自定義的背景效果 。
  • 修改makeSquareTextView方法, 讓聚合ICON顯示的我們自己的數字文字。
  • 修改getClusterText方法, 顯示我們需要的數字邏輯。
  • onBeforeClusterRendered 聚合圖示的渲染在這裡開始,我們可以修改成我們的邏輯。
  • perform 這是展示地圖上Marker的方法,在這裡修改載入邏輯,讓Marker支援顯示我們的動態圖示。

 顯示Marker的修改,是將原本只顯示drawable的功能,擴充套件到支援載入本地圖示的支援,後面我們只需要,把對應的Marker的圖示下載下來,就可以顯示不同的網路ICON啦。下方繼續長長長程式碼(。・・)ノ。

//聚合背景修改為我們要的效果
private LayerDrawable makeClusterBackground() {
    //讀取聚合圖示
    InputStream inputStream;
    inputStream = context.getResources().openRawResource(R.raw.cluster);
    BitmapDrawable drawable = new BitmapDrawable(inputStream);
    mColoredCircleBackground = drawable;
    //將外部的圈圈去掉
    ShapeDrawable outline = new ShapeDrawable(new RoundRectShape(mOuterCircle, mInsertCircle, null));
    outline.getPaint().setColor(0x00000000);
    LayerDrawable background = new LayerDrawable(new Drawable[]{outline, mColoredCircleBackground});
    //修改padding
    int strokeWidth = (int) (mDensity * 3);
    background.setLayerInset(1, strokeWidth, strokeWidth, strokeWidth, strokeWidth);
    return background;
}

/**
 * 設定文字的樣式,修改為使用了TextView,不用百度的SquareTextView
 */
private TextView makeSquareTextView(Context context) {
    TextView squareTextView =
            new TextView(context);
    ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams((int) (54 * mDensity), (int) (54 * mDensity));
    squareTextView.setLayoutParams(layoutParams);
    squareTextView.setId(R.id.text);
    squareTextView.setGravity(Gravity.CENTER);
    return squareTextView;
}

/**
 * 設定文字的數字顯示
 */
protected String getClusterText(int bucket) {
    if (bucket < BUCKETS[0]) {
        return String.valueOf(bucket);
    }
    if (bucket > 999) {
        bucket = 999;
        return String.valueOf(bucket) + "+";
    }
    return String.valueOf(bucket);
}

//顯示渲染圖示,修改註釋部分程式碼
private void perform(MarkerModifier markerModifier) {
    if (!shouldRenderAsCluster(cluster)) {
        /**如果此處沒有需要聚合的**/
        for (T item : cluster.getItems()) {
            Marker marker = mMarkerCache.get(item);
            MarkerWithPosition markerWithPosition;
            if (marker == null) {
                MarkerOptions markerOptions = new MarkerOptions();
                //markerOptions.animateType(MarkerOptions.MarkerAnimateType.grow);
                /**
                 * 下面就是marker的顯示
                 */
                BitmapDescriptor bitmapDescriptor;
                //如果有需要顯示的url icon的話,就顯示已下載的url Icon
                if (!TextUtils.isEmpty(item.getUrlLocalMarkerIconPath()) && new File(item.getUrlLocalMarkerIconPath()).exists()) {
                    bitmapDescriptor = item.getUrlMarkerIconBitmapDescriptor(false);
                    if (bitmapDescriptor == null) {
                        bitmapDescriptor = item.getBitmapDescriptor();
                    }
                } else {
                    bitmapDescriptor = item.getBitmapDescriptor();
                }

                ···
                onBeforeClusterItemRendered(item, markerOptions);
                marker = mClusterManager.getMarkerCollection().addMarker(markerOptions);
                markerWithPosition = new MarkerWithPosition(marker);
                //根據 item 快取marker
                mMarkerCache.put(item, marker);

                ···
               /**顯示聚合之前完善圖片資訊**/
               onBeforeClusterRendered(cluster, markerOptions);
         ····
}複製程式碼

 
5)、IconGenerator

 這個類主要是對應聚合Marker的,這裡修改了聚合圖示的大小,背景,文字樣式等等。其中setTextAppearance配置文字的樣式,如顏色,大小等,目前百度自帶Bubble.TextAppearance.Light和Bubble.TextAppearance.Dark,你也可以自己配置你需要的。

利用百度地圖實現支付寶 “到位” 功能(地圖模式)

3、LBS資料請求

 
 首先,你還是得有個女朋·····我呸,得是有個KEY,同樣是在百度地圖API,這次選擇的是服務端,建立應用後,最底部可以選擇sn加密或者的ip白名單。

 既然叫服務端,一般這種工作都是伺服器去做的,客戶端只需要從服務端拿資料就好。但是有時候服務端沒空理你(沒錯,就是不被理會的我),你就需要直接從百度伺服器扣了。

 這裡採用的是sn加密,就是根據url,通過申請到的ak和sk,還有各種請求引數,進行utf8轉化和md5加密,具體有興趣可以看看DEMO,雖然在客戶端這樣加密是沒有意義的。

那麼那我們就開始請求資料啦:

api.map.baidu.com/geosearch/v…

上方式一次請求中完整的url,其中:

  • geosearch/v3/nearby 代表中附近檢索模式,其他的還有支援區域(城市,全國,區)和矩形檢索模式。
  • ak 代表的就是你的服務端申請的對應資料ak
  • geotable_id 對應的是lbs資料的表名,你可以通過資料管理後臺直接加資料。
  • page_index 頁數,從0開始。
  • page_size 每頁返回資料,每頁最多50。
  • location 檢索的中心點。
  • radiud 檢索的半徑。
  • sn 加密後的sn。

其他的還有 有關鍵字q,標籤tag,排序sort,過濾等:

  • q 的檢索和熱詞分詞,具體能檢索到:設定的檢索欄位,地址,title等。
  • tag 對應百度的tag欄位,全匹配,可以設定逗號,空格分開。
    (以上兩個結合的話就是and關係喲,不是or關係)
  • sort排序只能排序int或者double的欄位
  • filter可以將檢索到的資料再過濾一遍,支援離線和區間。

還有其他的就在百度官網了,再說下去真的就廢話了。

 請求到資料後,lbs對應返回我們需要的json,我們對映成需要的model,組裝ClusterBaiduItem並設定圖示,用於ClusterManager渲染。

 但是!!!百度地圖Marker不支援url!這就尷尬了,所以我們還是乖乖自己下載吧┑( ̄Д  ̄)┍。

4、下載圖示

 
下載邏輯具體可看demo中的 ICONJob(真的好懶),主要的邏輯是:

  • 根據URL生成對應md5的檔名字,如果檔案存在,就通知marker更新。
  • 檔案不存在,下載(MD5+tmp)名字的檔案。
  • 下載完之後判斷檔案是否為圖片,是圖片的話就轉為需要大小的bitmp,然後儲存大圖(檔名為MD5 + BIG)和小圖(檔名為MD5),大圖用來做點選高亮,小圖用於正常顯示。
  • 按照上面邏輯一張一張順序的下載ICON到本地。

這裡主要是動態更新Marker的圖示邏輯:

 根據下載成功的ICON對應的id,找到對應的ClusterBaiduItem,通過ClusterBaiduItem,在ClusterManager的DefaultClusterRenderer中拿到marker,修改marker的圖示,為我們下載成功的圖片,對應的一一更新,哇塞,好簡單。

for (ClusterBaiduItem clusterBaiduItem : mClusterBaiduItems) {
    LBSModel lbsModel = clusterBaiduItem.getLBAModel();
    //此處根據id設定對應的圖片
    if (lbsModel.getUid() == e.geteId()) {
        BitmapDescriptor bitmapDescriptor;
        if (!TextUtils.isEmpty(clusterBaiduItem.getUrlLocalMarkerIconPath()) &&
                new File(clusterBaiduItem.getUrlLocalMarkerIconPath()).exists()) {
            bitmapDescriptor = clusterBaiduItem.getUrlMarkerIconBitmapDescriptor(false);
            if (bitmapDescriptor == null) {
                bitmapDescriptor = clusterBaiduItem.getBitmapDescriptor();
            }
        } else {
            bitmapDescriptor = clusterBaiduItem.getBitmapDescriptor();
        }
        //從聚合管理器裡面拿到marker,動態改變它
        Marker marker = mClusterManager.getDefaultClusterRenderer().getMarker(clusterBaiduItem);
        if (marker != null) {
            marker.setIcon(bitmapDescriptor);
        }
        //重新整理
        mClusterManager.cluster();
        return;
    }

}複製程式碼

利用百度地圖實現支付寶 “到位” 功能(地圖模式)
GIF效果

5、根據地圖狀態更新資料

 
 在地圖移動和縮放的時候,因為地理資訊改變了,需要更新資料,這時候可以通過setOnMapStatusChangeListener來監聽地圖的變化。

  • 首先如果沒有移動過,在移動開始的時候,就儲存當前最初狀態MapStatus,用來和後面的新的Status作對比。
  • 在狀態改變停止的時候,判斷是移動還是縮放。
  • 如果是縮放,或者移動操作設定好的距離引數,就進入等待請求資料,將儲存的Status清空。
  • 移動和縮放可能是連續性的,進入等待請求網路會設定一個1秒多的延時。
  • 如果一秒多沒有動作,就真正開始請求。
  • 如果又發生了變化,那麼就取消執行請求,重新發起請求的等待。
  • 每個請求會設定一個UUID作為請求的TAG,在資料返回的時候,判斷這一波資料對應的TAG,是不是當前頁面最後發起的,如果是的話,就接受,如果不是,我要你何用╮(╯_╰)╭。

mClusterManager.setOnMapStatusChangeListener(new BaiduMap.OnMapStatusChangeListener() {

    //記住變化前的上一個狀態
    private MapStatus mFrontMapStatus;

    @Override
    public void onMapStatusChangeStart(MapStatus mapStatus) {
        if (mFrontMapStatus == null) {
            mFrontMapStatus = mapStatus;
        }
    }
    @Override
    public void onMapStatusChangeFinish(MapStatus mapStatus) {
        //此處需要注意,如果是進入的時候重新定位了地址,或者進入後在改變地圖狀態,可能也會進入這裡
        if (mHadRequest) {
            if (StatusChangeLogic(mFrontMapStatus, mapStatus)) {//處理移動與放大
                mFrontMapStatus = null;
            }
        }
        mCurrentMapStatus = mapStatus;
    }
});

/**
 * 地圖因為操作而發生了狀態改變
 */
private boolean StatusChangeLogic(MapStatus frontMapStatus, MapStatus mapStatus) {
    //重新確定搜尋半徑的中心圖示
    mSearchModel.setGps(mapStatus.bound.getCenter().longitude + "," + mapStatus.bound.getCenter().latitude);
    //重新確定層級
    mSearchModel.setLevel(mapStatus.zoom);

    if (frontMapStatus == null)
        return false;

    //得到螢幕的距離大小
    double areaLength1 = DistanceUtil.getDistance(mapStatus.bound.northeast, mapStatus.bound.southwest);

    //計算螢幕的大小半徑
    int radius = (int) areaLength1 / 2;

    //重新確定搜尋的半徑
    mSearchModel.setRadius(radius);

    if (frontMapStatus.zoom == mapStatus.zoom) {
        if (frontMapStatus.bound == null)
            return false;
        //如果是移動了,得到距離
        double moveLenght = DistanceUtil.getDistance(frontMapStatus.bound.getCenter(), mapStatus.bound.getCenter());
        //如果移動距離大於螢幕的檢索半徑,請求資料
        if (moveLenght >= radius) {
            RequestNewDataLogic(true, true);
            return true;
        }

        //如果經緯度發生變化了,一般都是切換的城市之類的
        if (mChangeStatus != null && (mapStatus.target.latitude) != (int) (mChangeStatus.target.latitude)
                && (int) (mapStatus.target.longitude) != (int) (mChangeStatus.target.longitude) && mIsChangeCity) {
            RequestNewDataLogic(true, true);
            mIsChangeCity = false;
            return true;
        }

        return false;
    } else {
        //如果是縮放的話,地圖層級發生改變,重新請求資料
        RequestNewDataLogic(true, true);
        return true;
    }
}複製程式碼

   

5、點選放大Marker

 
效果:

利用百度地圖實現支付寶 “到位” 功能(地圖模式)

 點選放大,其實也是動態改變marker的圖示,根據ClusterBiaduItem拿到對應的marker,設定為大圖選中的Bitmap,廢話不說,下方擼碼。

//恢復上一個點選為正常狀態
if (mPreClickItem != null) {
    mPreClickItem.setBitmapId(R.drawable.default_map_icon);
    BitmapDescriptor bitmapDescriptor;
    //是否已經下載了ICON
    if (!TextUtils.isEmpty(mPreClickItem.getUrlLocalMarkerIconPath()) &&
            new File(mPreClickItem.getUrlLocalMarkerIconPath()).exists()) {
        bitmapDescriptor = mPreClickItem.getUrlMarkerIconBitmapDescriptor(false);
        if (bitmapDescriptor == null) {
            bitmapDescriptor = mPreClickItem.getBitmapDescriptor();
        }
    } else {
        bitmapDescriptor = mPreClickItem.getBitmapDescriptor();
    }
    //從聚合管理器裡面拿到marker,動態改變它
    Marker marker = mClusterManager.getDefaultClusterRenderer().getMarker(mPreClickItem);
    if (marker != null) {
        marker.setIcon(bitmapDescriptor);
    }
}
//設定新的點選為大圖狀態
if (clusterBaiduItem != null) {
    clusterBaiduItem.setBitmapId(R.drawable.default_map_icon_big);
    BitmapDescriptor bitmapDescriptor;
    //是否已經下載了ICON
    if (!TextUtils.isEmpty(clusterBaiduItem.getUrlLocalMarkerIconPath()) &&
            new File(clusterBaiduItem.getUrlLocalMarkerIconPath()).exists()) {
        bitmapDescriptor = clusterBaiduItem.getUrlMarkerIconBitmapDescriptor(true);
        if (bitmapDescriptor == null) {
            bitmapDescriptor = clusterBaiduItem.getBitmapDescriptor();
        }
    } else {
        bitmapDescriptor = clusterBaiduItem.getBitmapDescriptor();
    }
    //從聚合管理器裡面拿到marker,動態改變它
    Marker marker = mClusterManager.getDefaultClusterRenderer().getMarker(clusterBaiduItem);
    if (marker != null) {
        marker.setIcon(bitmapDescriptor);
    }
    //重新整理
    mClusterManager.cluster();

}
mPreClickItem = clusterBaiduItem;複製程式碼

6、點選聚合Marker,展開聚合到螢幕大小圖層

 
效果:

利用百度地圖實現支付寶 “到位” 功能(地圖模式)

 點選聚合展開,其實很簡單!<( ̄︶ ̄)>,將聚合的ClusterBaiduItem,用LatLngBounds.Builder ,將經緯度收集起來,之後通過MapStatusUpdateFactory生成新的mapstatus,BiaduMap使用animateMapStatus即可展開咯。

if (mBaiduMap == null) {
    return;
}
if (clusterBaiduItems.getItems().size() > 0) {
    LatLngBounds.Builder builder = new LatLngBounds.Builder();
    for (ClusterBaiduItem clusterBaiduItem : clusterBaiduItems.getItems()) {
        builder.include(clusterBaiduItem.getPosition());
    }
    mBaiduMap.animateMapStatus(MapStatusUpdateFactory
            .newLatLngBounds(builder.build()));
}複製程式碼

這裡大致上就是實現到點陣圖標的粗糙邏輯,最後還是通過DEMO擼起來,會更有感覺<( ̄ˇ ̄)/。

我是DEMO: github.com/CarGuo/LbsM… 使用者戳♂起來。

最後

  • 需要注意的,sn校驗的時候,需要注意請求引數的tag,q,filter中的逗號,‘&’,‘|’,‘/’d,空格等分割特殊符號的處理,不然很容易檢驗失敗。
  • 資料的返回,不一定是你要50就返回50,因為有增量更新的同步問題,所以有時候資料被刪除後,可能會導致某一頁只返回49等50以下的數量。
  • 最好不要開啟效果顯示Marker,因為Marker多了,會很卡,你可以針對某一個Marker去執行動畫,具體有drop,grow。
  • 切換城市等經緯度變化,也會使得地圖的MapStatus的回撥發生改變,這時候注意,不要因為觸發了縮放移動地圖的請求,你又主動發起了請求,從而同時產生兩個請求喲。
  • 如果使用百度定位,注意定位可能同時進入回撥幾次,注意不要因此發起好幾次請求喲。  

個人Github:github.com/CarGuo

利用百度地圖實現支付寶 “到位” 功能(地圖模式)
愁啊

相關文章