最近,项目有了新的需求,要求地图上的标签点实现聚合效果,单纯的marker已经无法满足大量数据展示的情况,聚合效果成为大势所趋。
目前,网上提供基于高德marker聚合的思路大致差不多,处于雏形阶段。高德官方也提供了关于聚合的解决方案,对于缓存和加载效率都做了一些处理,为我们后面的定制奠定了基础,本文就在高德官方提供的方案基础上做一些定制化。笔者经过思考后,还是觉得将篇幅分为上下两部分,前篇主要涉及聚合的基本使用以及针对定制过程中出现的坑进行填补(如多种聚合标签的冲突,聚合标签与普通标签的冲突问题等),下篇则讲述如何定制化marker并加载网络图片(为了方便描述,marker聚合在下文都称之为聚合标签)。看完两篇文章,大家还可以尝试将聚合和网络图片样式的marker结合起来,达到更加炫酷的效果。
话不多说,先来看看最终的效果吧:
先来放一下官网提供的聚合点的demo:https://github.com/amap-demo/android-cluster-marker
,如果只是实现一种聚合点,可以复制官方的demo代码,稍微改改就可以用了。如果你们产品有其他需求,比如多种聚合标签点,或者实现普通标签和聚合标签的混排使用,那么可以参考下本文提供的改进思路。
- 发现问题:
首先,我们发现高德官方提供的demo存在的问题:
1. 当存在多种类型聚合标签点时,聚合点的点击事件冲突问题.
这个问题是什么意思呢?举个例子:当地图上我们先后初始化两种聚合标签点A和B的时候,不管点击A还是B类型的聚合点,都会只响应B类的点击事件,即先声明的聚合标签的点击事件会被后声明的聚合标签的点击事件”抢占”.
2. 当地图上存在普通标签(Marker)和聚合标签(Cluster)时,同样会产生点击事件的冲突问题.
这个意思就是,点击聚合标签仍然会响应普通标签的点击事件.
- 分析问题:
这两个问题其实归根结底是一个问题,都是因为marker的点击事件被抢占,从官方的提供的ClusterOverlay类中代码我们就可以看出来:
......
amap.setOnCameraChangeListener(this);
amap.setOnMarkerClickListener(this);
......
//点击事件
@Override
public boolean onMarkerClick(Marker arg0) {
if (mClusterClickListener == null) {
return true;
}
Cluster cluster= (Cluster) arg0.getObject();
if(cluster!=null){
mClusterClickListener.onClick(arg0,cluster.getClusterItems());
return true;
}
return false;
}
从以上代码我们可以看出来,聚合处理类ClusterOverlay实现了AMap.OnMarkerClickListener接口,而当我们使用两个或者两个以上样式的聚合标签时,我们就会发现明明是两种类型的聚合标签,但是却触发相同的点击事件。这样显然是不符合我们预期需求的,那么我们该如何让不同样式的聚合标签A,B,C…各司其职呢?你肯定会说,废话,不然你现在在讲啥!咳咳,请允许我静静~
- 解决问题:
显然,如果我们想让各种聚合标签互不干扰,需要集中管理它们的点击事件,即OnMarkerClickListener。假设我们写了两个不同的聚合标签类ClusterOverlayA和ClusterOverlayB,我们可以将两个类中OnMarkerClick()方法中的逻辑拿出来,单独封装成一个方法,然后放在我们Activity的onMarkerClick()中执行.这里我贴一下修改后的ClusterOverlay类,前方高能,一大波代码来袭:
ClusterOverlay:
/**
* Created by yiyi.qi on 16/10/10.
* 整体设计采用了两个线程,一个线程用于计算组织聚合数据,一个线程负责处理Marker相关操作
*/
public class ClusterOverlay {
private AMap mAMap;
private Context mContext;
private List<ClusterItem> mClusterItems;
private List<Cluster> mClusters;
private int mClusterSize;
private ClusterClickListener mClusterClickListener;
private ClusterRender mClusterRender;
private ClusterRenderB bClusterRender;
private AMap.OnMarkerClickListener markerClickListener=new AMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker arg0) {
if (mClusterClickListener == null) {
return true;
}
Cluster cluster = (Cluster) arg0.getObject();
//将marker的位置移动到地图中心
LatLng latLng=new LatLng(arg0.getPosition().latitude,arg0.getPosition().longitude);
mAMap.moveCamera(CameraUpdateFactory.changeLatLng(latLng));
if (cluster != null) {
arg0.showInfoWindow();
Log.e("timeory", "普通标签infowindow出现了吗? " + arg0.isInfoWindowShown()+ " "+ arg0.isInfoWindowEnable(), null);
mClusterClickListener.onClick(arg0, cluster.getClusterItems());
return true;
}
return false;
}
};
private List<Marker> mAddMarkers = new ArrayList<Marker>();
private double mClusterDistance;
private LruCache<Integer, BitmapDescriptor> mLruCache;
private HandlerThread mMarkerHandlerThread = new HandlerThread("addMarker");
private HandlerThread mSignClusterThread = new HandlerThread("calculateCluster");
private Handler mMarkerhandler;
private Handler mSignClusterHandler;
private float mPXInMeters;
private boolean mIsCanceled = false;
private MarkerOptions markerOptions;
private Cluster mcluster;
private LatLng latlng1;
/**
* 构造函数
*
* @param amap
* @param clusterSize 聚合范围的大小(指点像素单位距离内的点会聚合到一个点显示)
* @param context
*/
public ClusterOverlay(AMap amap, int clusterSize, Context context) {
this(amap, null, clusterSize, context);
}
/**
* 构造函数,批量添加聚合元素时,调用此构造函数
* 默认最多会缓存80张图片作为聚合显示元素图片,根据自己显示需求和app使用内存情况,可以修改数量
* @param amap
* @param clusterItems 聚合元素
* @param clusterSize
* @param context
*/
public ClusterOverlay(AMap amap, List<ClusterItem> clusterItems, int clusterSize, Context context) {
mLruCache = new LruCache<Integer, BitmapDescriptor>(80) {
protected void entryRemoved(boolean evicted, Integer key, BitmapDescriptor oldValue, BitmapDescriptor newValue) {
oldValue.getBitmap().recycle();
}
};
if (clusterItems != null) {
mClusterItems = clusterItems;
} else {
mClusterItems = new ArrayList<ClusterItem>();
}
mContext = context;
mClusters = new ArrayList<Cluster>();
this.mAMap = amap;
mClusterSize = clusterSize;
mPXInMeters = mAMap.getScalePerPixel(