四个月前公司新项目,要求首页仿链家实现地图定位点多级聚合,效果图如下:
好吧,我们没效果图,暂时拿链家的效果图代替一下:
效果大概是这个样子,而我们要求比链家更深一级,当时看到这需求和效果,第一想法就是下载百度官方 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()+"");
}
九、打完收工,第一次写这么长的博客,虽然写的比较乱,但真的用心了!有什么不懂的可以问我,希望可以帮助那些找不到解决方案的伙伴。如果文章对你有用,请点个赞!
最后,最近发现很多朋友要demo,可能写的比较杂乱,看起来难以理解,所以写了个小demo,本想设置1积分下载,但是发现现在系统起步默认5积分,需要下载的朋友自己视情况下载吧,demo下载地址:
https://download.csdn.net/download/baidu_36600645/11223113