Android Google Maps Utils ClusterManager Change Icon Text, Size And Color

August 14, 2017
Hack DefaultClusterRenderer.

Google Maps Utils Cluster Manager cluster markers together into clusters (round circle icon with number) to avoid showing too many markers.

The ClusterManager helps you manage multiple markers at different zoom levels. This means you can put a large number of markers on a map without making the map hard to read. When a user views the map at a high zoom level, the individual markers show on the map. When the user zooms out to a lower zoom level, the markers gather together into clusters, to make viewing the map easier.

This article focus on how change the text, size and color of the cluster icons.

Hack DefaultClusterRenderer

DefaultClusterRenderer does not expose most of its variables and functions (private only), so the only way to expose them is to copy the code and modify them with your own implementation.

Copy the code of DefaultClusterRenderer and rename the class as MyClusterRenderer.

  • Edit makeSquareTextView to change font size, style and padding, which would affect the cluster oval icon’s size. Remember to remove mIconGenerator.setTextAppearance to prevent text style to be overwritten.
  • Edit getColor to change the cluster oval icon’s background color.
  • Edit onBeforeClusterRendered or getClusterText to change cluster oval icon’s text.
public class MyClusterRenderer<T extends ClusterItem> implements ClusterRenderer<T> {
    ...

    public NumberClusterRenderer(Context context, GoogleMap map, ClusterManager<T> clusterManager) {
        mMap = map;
        mAnimate = true;
        mDensity = context.getResources().getDisplayMetrics().density;
        mIconGenerator = new IconGenerator(context);
        mIconGenerator.setContentView(makeSquareTextView(context));
        // comment this line to prevent text style being applied and overwriting makeSquareTextView settings
        // mIconGenerator.setTextAppearance(R.style.amu_ClusterIcon_TextAppearance);
        mIconGenerator.setBackground(makeClusterBackground());

        mClusterManager = clusterManager;
    }
    ...
    // the oval shape of cluster icon size depends mostly on the text and padding size
    private SquareTextView makeSquareTextView(Context context) {
        SquareTextView squareTextView = new SquareTextView(context);
        // added the following 3 lines to change text size, color and bold
        // I make the text smaller, color and bold is required since we removed R.style.amu_ClusterIcon_TextAppearance
        squareTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
        squareTextView.setTextColor(Color.WHITE);
        squareTextView.setTypeface(null, Typeface.BOLD);

        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        squareTextView.setLayoutParams(layoutParams);
        squareTextView.setId(R.id.amu_text);
        // initial is 12dp, I resize the round shape padding to 6dp
        int twelveDpi = (int) (6 * mDensity);
        squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi);
        return squareTextView;
    }
    ...
    protected int getColor(int clusterSize) {
        /*
        final float hueRange = 220;
        final float sizeRange = 300;
        final float size = Math.min(clusterSize, sizeRange);
        final float hue = (sizeRange - size) * (sizeRange - size) / (sizeRange * sizeRange) * hueRange;
        return Color.HSVToColor(new float[]{
                hue, 1f, .6f
        });
         */

        // I use Cyan color palette: https://material.io/guidelines/style/color.html#color-color-palette
        // refer to variable BUCKETS for available cluster size
        String color = "#80DEEA";
        switch(clusterSize) {
            case 10:
                color = "#4DD0E1";
                break;
            case 20:
                color = "#26C6DA";
                break;
            case 50:
                color = "#00BCD4";
                break;
            case 100:
                color = "#00ACC1";
                break;
            case 200:
                color = "#0097A7";
                break;
            case 500:
                color = "#00838F";
                break;
            case 1000:
                color = "#006064";
                break;
        }
        return Color.parseColor(color);
    }
    ...
    protected void onBeforeClusterRendered(Cluster<T> cluster, MarkerOptions markerOptions) {
        int bucket = getBucket(cluster);
        BitmapDescriptor descriptor = mIcons.get(bucket);
        if (descriptor == null) {
            mColoredCircleBackground.getPaint().setColor(getColor(bucket));
            // you can edit/replace getClusterText to change text of cluster icon
            descriptor = BitmapDescriptorFactory.fromBitmap(mIconGenerator.makeIcon(getClusterText(bucket)));
            mIcons.put(bucket, descriptor);
        }
        // TODO: consider adding anchor(.5, .5) (Individual markers will overlap more often)
        markerOptions.icon(descriptor);
    }
    ...    
}

Load our custom MyClusterRenderer.

mapFragment.getMapAsync(new OnMapReadyCallback() {
    @Override
    public void onMapReady(final GoogleMap map) {
        ClusterManager clusterManager = new ClusterManager(activity, map);
        final MyClusterRenderer renderer = new NumberClusterRenderer(activity, map, clusterManager);
        // use this to show cluster only without markers
        renderer.setMinClusterSize(0);
        clusterManager.setRenderer(renderer);
    }
});
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.