5

I am using Google Maps V2 for Android together with maps utils extension library for marker clustering. Some parts of app does not need to get clustered markers.

Is there any way to forbid clusterManager to cluster markers and after certain conditions let it cluster items again?

3 Answers3

15

I found myself another solution. I figured out that on DefaultClusterRenderer method shouldRenderAsCluster is responsible for whether the marker will be rendered as cluster or not. So I created a CustomRenderer class which extends DefaultClusterRenderer and created a method with a boolean variable to determine whether renderer should cluster or not.

public class CustomRenderer extends DefaultClusterRenderer<MarkerItem>
{
private boolean shouldCluster = true;
private static final int MIN_CLUSTER_SIZE = 1;

//Some code....

public void setMarkersToCluster(boolean toCluster)
{
    this.shouldCluster = toCluster;
}

I also override the method here that i mentioned before.

@Override
protected boolean shouldRenderAsCluster(Cluster<MarkerItem> cluster)
{
    if (shouldCluster)
    {
        return cluster.getSize() > MIN_CLUSTER_SIZE;
    }

    else
    {
        return shouldCluster;
    }
}

And now if I want to stop clustering i just call this method from the activity i want.

ClusterManager clusterManager = new ClusterManager<MarkerItem>(this, googleMap);
CustomRenderer customRenderer = new CustomRenderer(this, googleMap, clusterManager);
clusterManager.setRenderer(customRenderer);
customRenderer.setMarkersToCluster(false);
  • Very useful approach if you only want to cluster down to a certain zoom level, in the map's `onCameraChange`, before calling the cluster manager's own `onCameraChange`, you can check the current zoom level and set `shouldCluster` appropriately. – MPelletier Dec 28 '15 at 17:36
  • Indeed. I am actually using this approach as well – Arūnas Bedžinskas Dec 29 '15 at 14:27
2

The cluster manager implementation is only capable of performing the built in clustering functionality. If you wish for certain markers to not cluster, you will need to add those markers to the map directly. When you decide to cluster those markers, you will need to remove them from the map and transfer their information to the cluster manager for it to take over.

ian.shaun.thomas
  • 3,468
  • 25
  • 40
  • If I add those certain markers to the map directly, i cannot show info window on marker click since cluster manager is handling the marker clicks from the beginning – Arūnas Bedžinskas Dec 03 '14 at 14:21
  • You can handle the clicks anywhere. For my use cases I wrote a map manager that helps wrap up functionality similar to what you are after. Essentially my manager handles all marker clicks regardless of if they came from the cluster manager or from the map directly. I would using a similar approach to help you wrap up all the functionality you need. TLDR, I also created a way to generically map the markers back to the right data, if you are already handling cluster clicks it sounds like you have run into the issue already that handling cluster clicks is annoying at best. – ian.shaun.thomas Dec 03 '14 at 14:41
  • Yeah, that would be perfect for me, however im currently failing to figure out how to capture both clustermanager marker clicks and direct map on clicks since you have to direct different listeners for each of those. Any suggestion how did you implement this approach? – Arūnas Bedžinskas Dec 03 '14 at 14:45
  • 1
    http://goo.gl/pgdTtX In this implementation I ended up using intents to fire broadcasts so the clicks can be handled anywhere with context but this wasn't a requirement to handle the click. Essentially my renderer retains a map of data to do lookups of data based on the selected marker. If you consider this system, your marker clicks on the map can implement a similar system to your renderer and you can use a central handler of the intents (or whatever your implementation is) to handle the clicks generically regardless of source. This is not an absolute solution though, just one that worked. – ian.shaun.thomas Dec 03 '14 at 17:20
  • In need of this solution, but the link is broken :/ – Starwave Jan 11 '17 at 11:00
  • I guess, its this one (https://github.com/cforlando/bike-orlando-android/blob/master/app/src/main/java/com/codefororlando/transport/controller/ClusterRenderer.java), but I still fail to see where is the function that catches click event – Starwave Jan 11 '17 at 11:04
0

After calling setMarkersToCluster(false) to disable clustering it tooks a while, before shouldRenderAsCluster(Cluster cluster) of the CustomRenderer is called. So if renderer.getMarker(markerItem) is called directly after disabling clustering null is returned. Waiting for some time by:

    new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
public void run(){
    Marker marker=renderer.getMarker(markerItem);
}

}, 700L);

returns a marker !=null.

This is not an ideal solution, because the waiting time might not be always work. An alternative would be to implement an event listener and an event publisher (visit Java. Correct pattern for implementing listeners) The event is produced in

    @Override
protected boolean shouldRenderAsCluster(Cluster<MarkerItem> cluster) { ...}

and sent to the observer, which was registered by the event producer.

     @Override
protected boolean shouldRenderAsCluster(Cluster<MarkerItem> cluster) {
    //start clustering if at least 5 items overlap and mShouldCluster==true
    int clusterSize = cluster.getSize();
    boolean clusteringOn=(mShouldCluster && (clusterSize > 4));
    notifyObserver(clusteringOn);
    final String msg = "shouldRenderAsCluster; act. cluster size= = " + cluster.getSize() + ". should cluster? " +  clusteringOn;
    if (BuildConfig.DEBUG) {
        Log.i(LOG_TAG, "+++" + msg);
    }
    return clusteringOn;
}

     /**
 * Renderer Settings
 * @param shouldRender =true: rendering enabled; =false: rendering disabled
 */
public void setShouldRender(boolean shouldRender) {
    mShouldCluster = shouldRender;
}
/**
 * Register the observer at the event handler
 * @param  observer event listener of the observer
 */
public void setShouldRenderListener(IOnShouldRendererListener observer) {
    mObserver=observer;
}
/**
 * Notify the observer, if the observer ist registered at the event handler,
 * @param shouldRender =true; rendering enabled; =false: rendering disabled
 */
private void notifyObserver(boolean shouldRender) {
    // Notify the observer
    if (mObserver!=null) {
        mObserver.onShouldClusterCalled(shouldRender);
    }
}

The event listener implemented in the observer class MarkerCollection2 implements IOnShouldRendererListener.

    public interface IOnShouldRendererListener {
 void onShouldClusterCalled(boolean shouldRender);
}

public void onShouldClusterCalled(boolean shouldCluster) {
    Log.i(LOG_TAG,"onShouldClusterCalled; should Cluster= " + shouldCluster);
    if (!shouldCluster){
        final MarkerItem markerItem = mMarkerList.get(mLastSelectedMarker);
        final Marker marker = mClusterRenderer.getMarker(markerItem);
        Log.i(LOG_TAG,"onShouldClusterCalled; marker!=null? " + (marker!=null));
        if (marker!=null) {
            mClusterRenderer.setShouldRender(true);
            mClusterRenderer.setShouldRenderListener(null);
            mMyInfoWindowAdapter.setClickedMarkerItem(markerItem);
            LatLng position = markerItem.getPosition();
            final CameraUpdate update;
            if (markerItem instanceof RouteMarkerItem) {
                //marker is a non clustered route marker: nothing to zoom
                update = CameraUpdateFactory.newLatLng(position);
            } else {
                // marker is a clustered station marker; zoom to MinZoomLevelNotClustered
                update = CameraUpdateFactory.newLatLngZoom(position, MIN_ZOOM_NOT_CLUSTERED);
            }
            //showInfoWindow, must run on UI thread
            final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                public void run() {
                    marker.showInfoWindow();
                    mMap.moveCamera(update);
                }
            });
        }
    }
}

Starting showInfoWindow is done by:

     mClusterRenderer.setShouldRenderListener(this);
    mClusterRenderer.setShouldRender(false);
    mClusterManager.cluster();

When onShouldClusterCalled was called, the listener is set to null to prevent further notifications and rendering is enabled again. This works, but this is a too complex solution only for open an infoWindow. So I will stay with the maps extension library: android-maps-extension

This library does the clustering of markers and GoogleMap markers can be accessed and marker.showInfoWindow() can be called.T