四個月前公司新項目,要求首頁仿鏈家實現地圖定位點多級聚合,效果圖如下:
好吧,我們沒效果圖,暫時拿鏈家的效果圖代替一下:
效果大概是這個樣子,而我們要求比鏈家更深一級,當時看到這需求和效果,第一想法就是下載百度官方 demo,下載運行後發現很不適用,然後嘗試着修改官方源碼,結果從入門改到放棄......
然後,網上各種搜索,找了一天發現幾乎沒有這方面寫的比較詳細、實用的文章......
只能自己開闢思路寫了,後來終於發現把問題想的太複雜了,被自己帶到溝裏去了,看似點聚合,實則是多點位標記。功能比較全,代碼比較多,耐心看下去或許會有收穫。不廢話了,說一下解決方案:
一、先獲取定位點,爲了方便使用可以採用 sql 存儲,然後根據 parentId 劃分出定位點級別,這裏就不普及 sql 的用法了,不會的自行百度,這裏要注意判斷經緯度是否爲空:
//獲得行政區 getEqpAreas(result); //清空數據庫 dbOperatorService.EmptyEQPAreas(); //添加數據庫 dbOperatorService.SaveEQPAreas(areasList);
二、添加百度地圖加載,很簡單,但是這裏要先有思路,根據自己需要展示幾層聚合,設置好地圖縮放的“級別段”,可能有些人會迷糊,這裏簡單普及一下;
1、百度地圖好像一共21級別,級別越大,地圖範圍越小。級別越小,地圖範圍越大。
1級 :10000公里 2級:5000公里 3級:2000公里 4級:1000公里 5級:500公里 6級:200公里 7級:100公里
8級 : 50公里 9級:25公里 10級:20公里 11級:10公里 12級:5公里 13級:2公里 14級:1公里
15級:500米 16級:200米 17級:100米 18級:50米 19級:20米 20級:10米 21級:5米
大概是這個樣子。
而在使用中前8級可以直接忽略,20、21級可以根據自己需求選擇,好了,介紹完地圖級別,就要說什麼叫縮放“級別段”了。
要實現效果圖中 “聚合點” 縮放樣式,只能根據地圖縮放等級來設置,先設置好地圖縮放最小、最大級別,然後縮放深度有幾層,就把地圖展示級別分爲幾個層次,這個層次就叫“級別段”。怎麼劃分就看個人需求了。
2、添加地圖展示,並設置最大、最小級別以及定位,這些就不科普了,不會的自行百度。
baiduMap = mapView.getMap(); //設置默認位置 LatLng latLng = new LatLng(30.280782,120.121143); status = new MapStatus.Builder().target(latLng).zoom(11).build(); baiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(status)); //是否顯示控制地圖等級縮小、放大按鈕 mapView.showZoomControls(false); //打開定位圖層 baiduMap.setMyLocationEnabled(true); //聲明 LocationClient 類 client = new LocationClient(getContext()); //配置定位 SDK 參數 location(); //註冊監聽函數 client.registerLocationListener(listener); //開啓定位 client.start(); //圖片點擊事件,回到固定點 client.requestLocation(); //地圖縮放等級 baiduMap.setMaxAndMinZoomLevel(21, 9);
三、把得到的定位點按等級分別添加到集合中,並且在地圖上添加標記點,這裏只舉一層爲例;
private void setOneMarker() { cityAreaList = new ArrayList<>(); // 從 Sql 中取出對應等級的定位點 cityAreaList = dbOperatorService.GetEQPAreasOneGrade(); if (cityAreaList != null && cityAreaList.size() > 0) { for (int i = 0; i < cityAreaList.size(); i++) { EQCityArea cityArea = cityAreaList.get(i); addMarket(cityArea); } } else { Toast.makeText(getContext(), "暫無標記點", Toast.LENGTH_SHORT).show(); } } // 標記 Market private void addMarket(EQCityArea cityArea) { if (AppUtil.isNotEmpty(cityArea.getLatitude()) && AppUtil.isNotEmpty(cityArea.getLongitude())) { Double lat = Double.valueOf(cityArea.getLatitude()) / 1000000; Double lon = Double.valueOf(cityArea.getLongitude()) / 1000000; String content = cityArea.getName(); LatLng latLng = new LatLng(lat, lon); // 定位點樣式根據自己需求設置 View view = LayoutInflater.from(getContext()).inflate(R.layout.maptext_layout, null, false); TextView tv_adress = view.findViewById(R.id.tv_adress); TextView text = view.findViewById(R.id.text); text.setVisibility(View.GONE); tv_adress.setText(content); BitmapDescriptor descriptorOne = BitmapDescriptorFactory.fromView(view); MarkerOptions options = new MarkerOptions().icon(descriptorOne).position(latLng).zIndex(9) .animateType(MarkerOptions.MarkerAnimateType.grow); Marker markerOne = (Marker) baiduMap.addOverlay(options); markerOne.setVisible(false); markerOneList.add(markerOne); latLngOneList.add(latLng); }
第二層、第三層獲取、添加標記同理。
四、很重要、很關鍵!添加地圖狀態發生改變時監聽地圖縮放等級,
baiduMap.setOnMapStatusChangeListener(new BaiduMap.OnMapStatusChangeListener() { @Override public void onMapStatusChangeStart(MapStatus mapStatus) { } @Override public void onMapStatusChangeStart(MapStatus mapStatus, int i) { } @Override public void onMapStatusChange(MapStatus mapStatus) { } @Override public void onMapStatusChangeFinish(MapStatus mapStatus) { }
五、在 onMapStatusChangeFinish() 方法中獲取當前地圖縮放等級,並且獲取最後一級詳細點位;
1、獲取當前地圖縮放級別以及手機當前屏幕地圖中心點
// 獲得當前地圖等級
zoomSize = baiduMap.getMapStatus().zoom;
Log.e("Start", zoomSize + "");
// 獲取屏幕中心點座標
LatLng latLng = mapStatus.target;
Log.e("", latLng + "");
2、根據自己設定的最大地圖級別進行判斷,這裏根據個人需求,如果聚合最後一級和鏈家一樣要求滑動加載某個點的詳情,就添加是否大於縮放最大級別的判斷,如果只是縮放,只需要添加是否小於縮放最大級別的判斷就行了:
@Override public void onMapStatusChangeFinish(MapStatus mapStatus) { // 獲得當前地圖等級 zoomSize = baiduMap.getMapStatus().zoom; Log.e("Start", zoomSize + ""); // 獲取屏幕中心點座標 LatLng latLng = mapStatus.target; Log.e("", latLng + ""); if (zoomSize > MAPSIZE_EIGHTEEN) { //噹噹前地圖級別大於 18 時 if (baiduMap.getProjection() != null) { //獲取經緯度區域內監控 getMonitor(latLng); //獲取經緯度區域內社會資源 getResourcesByLatLng(latLng); } } else if (zoomSize <= MAPSIZE_EIGHTEEN) { //噹噹前地圖級別小於 18 時 // 如果詳細點點擊有氣泡加上這句去氣泡,如果沒有就不用寫這句 baiduMap.hideInfoWindow(); //監控點 if (markerPoint != null && markerPoint.size() > 0) { for (int i = 0; i < markerPoint.size(); i++) { markerMoni = markerPoint.get(i); markerMoni.setVisible(false); } } //社會資源 if (resourMarkerList!=null&&resourMarkerList.size()>0){ for (int i = 0; i < resourMarkerList.size(); i++) { resourMaker = resourMarkerList.get(i); resourMaker.setVisible(false); } } if (view != null) { if (view.getVisibility() == View.VISIBLE) { view.setVisibility(View.GONE); } } // 爲了防止異常,把集合清空一下 if (monitorList != null && monitorList.size() > 0) { monitorList.clear(); } if (monitorSet != null && monitorSet.size() > 0) { monitorSet.clear(); } if (markerPoint != null && markerPoint.size() > 0) { markerPoint.clear(); } if (publicResourceList !=null && publicResourceList.size()>0){ publicResourceList.clear(); } if (resourceHashSet!=null&&resourceHashSet.size()>0){ resourceHashSet.clear(); } } if (baiduMap.getProjection()!=null){ //獲取屏幕中心點 updateMapState(); } }
六、在 updateMapState() 方法中通過獲取當前屏幕地圖範圍最大值展示標記點,防止同時標記過多出現屏幕卡頓,並通過判斷地圖級別段對 Marker 的展示進行控制:
1、獲取屏幕地圖範圍
WindowManager manager = getActivity().getWindowManager(); DisplayMetrics metrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(metrics); //獲取屏幕的寬和高 int width = metrics.widthPixels; int height = metrics.heightPixels; Point point = new Point(); point.x = 0; point.y = 0; final LatLng llL = baiduMap.getProjection().fromScreenLocation(point);//左上角經緯度 Log.e("", llL + ""); Point ptR = new Point(); ptR.x = width; ptR.y = height; final LatLng llR = baiduMap.getProjection().fromScreenLocation(ptR);//右下角經緯度 Log.e("", llR + "");
2,先判斷每一層的定位點是否在屏幕座標範圍內,在內就展示,不在就隱藏。另外根據縮放級別選擇展示哪一層,隱藏哪一層,這裏以第一層和第二層舉個例子:
先把變量給出來,不然不太好懂:
//第一層聚合 Marker markerOne; private List<EQCityArea> cityAreaList; private List<Marker> markerOneList = new ArrayList<>(); private List<LatLng> latLngOneList = new ArrayList<>(); //第二層 Marker markerTwo; private List<EQStreet> streetList; private List<Marker> markerTwoList = new ArrayList<>(); private List<LatLng> latLngTwoList = new ArrayList<>();
開始進行判斷:
if (zoomSize <= MAPSIZE_TWELVE) { // 小於 12 級別 if (latLngOneList != null && latLngOneList.size() > 0) { for (int i = 0; i < latLngOneList.size(); i++) { LatLng latLng = latLngOneList.get(i); Double lat = latLng.latitude; Double lon = latLng.longitude; if (llR.latitude < lat && lat < llL.latitude && llL.longitude < lon && lon < llR.longitude) { markerOne = markerOneList.get(i); markerOne.setVisible(true); } else { markerOne = markerOneList.get(i); markerOne.setVisible(false); } } if (markerTwoList != null && markerTwoList.size() > 0) { for (int i = 0; i < markerTwoList.size(); i++) { markerTwo = markerTwoList.get(i); markerTwo.setVisible(false); } } if (markerThreeList != null && markerThreeList.size() > 0) { for (int i = 0; i < markerThreeList.size(); i++) { markerThree = markerThreeList.get(i); markerThree.setVisible(false); } } } else { //如果第一層沒數據 if (latLngTwoList != null && latLngTwoList.size() > 0) { for (int i = 0; i < latLngTwoList.size(); i++) { LatLng latLng = latLngTwoList.get(i); Double lat = latLng.latitude; Double lon = latLng.longitude; if (llR.latitude < lat && lat < llL.latitude && llL.longitude < lon && lon < llR.longitude) { markerTwo = markerTwoList.get(i); markerTwo.setVisible(true); } else { markerTwo = markerTwoList.get(i); markerTwo.setVisible(false); } } } if (markerOneList != null && markerOneList.size() > 0) { for (int i = 0; i < markerOneList.size(); i++) { markerOne = markerOneList.get(i); markerOne.setVisible(false); } } if (markerThreeList != null && markerThreeList.size() > 0) { for (int i = 0; i < markerThreeList.size(); i++) { markerThree = markerThreeList.get(i); markerThree.setVisible(false); } } } } else if (zoomSize > MAPSIZE_TWELVE && zoomSize <= MAPSIZE_FIFTEEN) {// 12 到 15 if (latLngOneList != null && latLngOneList.size() > 0) { if (latLngTwoList != null && latLngTwoList.size() > 0) { for (int i = 0; i < latLngTwoList.size(); i++) { LatLng latLng = latLngTwoList.get(i); Double lat = latLng.latitude; Double lon = latLng.longitude; if (llR.latitude < lat && lat < llL.latitude && llL.longitude < lon && lon < llR.longitude) { markerTwo = markerTwoList.get(i); markerTwo.setVisible(true); } else { markerTwo = markerTwoList.get(i); markerTwo.setVisible(false); } } } if (markerOneList != null && markerOneList.size() > 0) { for (int i = 0; i < markerOneList.size(); i++) { markerOne = markerOneList.get(i); markerOne.setVisible(false); } } if (markerThreeList != null && markerThreeList.size() > 0) { for (int i = 0; i < markerThreeList.size(); i++) { markerThree = markerThreeList.get(i); markerThree.setVisible(false); } } }
七、寫到這裏通過手指縮放屏幕,聚合功能已經可以展示出來了,但是並不完美,作爲程序狗,我們要省事,所以我們還需要再添加一個功能,當用戶點擊最外層聚合點時間直接進入下一層,這裏就要用到 Marker 點擊事件了,這裏如果你只有聚合功能只需要要控制增加地圖層次級別就好了:
baiduMap.setOnMarkerClickListener(new BaiduMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { if (zoomSize < 18) { zoomSize += 3; // 3 是我的地圖級別段長度,根據自己設置的填寫 status = new MapStatus.Builder().zoom(zoomSize).target(marker.getPosition()).build(); baiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(status)); } } }
當然大多數功能並不會這麼簡單,往往需求還會讓點擊定位點彈出氣泡:
if (zoomSize < 18) { zoomSize += 3; status = new MapStatus.Builder().zoom(zoomSize).target(marker.getPosition()).build(); baiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(status)); } else {
// 填寫自己點擊氣泡要展示的 View
// 創建 InfoWindow , 傳入 View 、 Latlng 、Y 軸偏移量 InfoWindow window = new InfoWindow(view, marker.getPosition(), -80); baiduMap.showInfoWindow(window); }
八,最後再來講解一下,怎麼仿鏈家滑動屏幕加載新數據,並且不重複加載老數據,這裏就要用到 Set 集合:
1、先是在設置實體類是添加
public boolean equals(Object o) {} public int hashCode() {}
2、先把獲取到的定位點添加到 ArrayList 集合中,再遍歷集合中的定位點是不是在屏幕範圍內,在就添加到 Set 集合中,然後根據 Set 集合添加 Marker:
// 把獲取到的點添加到 ArrayList 集合中 resourceList.add(resource);
// 判斷點是否在屏幕內 WindowManager manager = getActivity().getWindowManager(); DisplayMetrics metrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(metrics); //獲取屏幕的寬和高 int width = metrics.widthPixels; int height = metrics.heightPixels; Point point = new Point(); point.x = 0; point.y = 0; llL = baiduMap.getProjection().fromScreenLocation(point);//左上角經緯度 Log.e("", llL + ""); Point ptR = new Point(); ptR.x = width; ptR.y = height; llR = baiduMap.getProjection().fromScreenLocation(ptR);//右下角經緯度 Set<MoniPoint> resourceSet = new HashSet<>(); if (resourceList!=null&&resourceList.size()>0){ for (int i=0;i<resourceList.size();i++){ Social resource = resourceList.get(i); Double lat = Double.valueOf(resource.getLatitude())/1000000; Double lon = Double.valueOf(resource.getLongitude())/1000000; if (llR.latitude < lat && lat < llL.latitude && llL.longitude < lon && lon < llR.longitude) { boolean isPoint = resourceHashSet.contains(resource); if (isPoint) { } else{ resourceHashSet.add(resource); Log.e("",resourceHashSet.size()+""); // 添加繪製 Marker 的方法 drawResource(resource); } } } }
private void drawResource(Social resource) { publicResourceList.add(resource); Double lat = Double.valueOf(resource.getLatitude())/1000000; Double lon = Double.valueOf(resource.getLongitude())/1000000; LatLng latLng = new LatLng(lat, lon); BitmapDescriptor descriptor = BitmapDescriptorFactory.fromResource(R.drawable.icon_shehuizy_map); MarkerOptions options = new MarkerOptions().icon(descriptor).position(latLng).zIndex(9) .animateType(MarkerOptions.MarkerAnimateType.grow); Marker marker = (Marker) baiduMap.addOverlay(options); marker.setVisible(true); resourMarkerList.add(marker); Log.e("",resourMarkerList.size()+""); }
九、打完收工,第一次寫這麼長的博客,雖然寫的比較亂,但真的用心了!有什麼不懂的可以問我,希望可以幫助那些找不到解決方案的夥伴。