When I have a cluster 17, 18, it shows "10+", "20+". How can I display the exact number?
Thanks.
public class MyClusterItemRenderer extends DefaultClusterRenderer<MyClusterItem> {
private final IconGenerator mIconGenerator;
private final ShapeDrawable mColoredCircleBackground;
private final float mDensity;
private SparseArray<BitmapDescriptor> mIcons = new SparseArray();
public MyClusterItemRenderer(Context context, GoogleMap map, ClusterManager<MyClusterItem> clusterManager) {
super(context, map, clusterManager);
mIconGenerator = new IconGenerator(context);
mColoredCircleBackground = new ShapeDrawable(new OvalShape());
mDensity = context.getResources().getDisplayMetrics().density;
SquareTextView squareTextView = new SquareTextView(context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(-2, -2);
squareTextView.setLayoutParams(layoutParams);
squareTextView.setId(com.google.maps.android.R.id.text);
int twelveDpi = (int)(12.0F * this.mDensity);
squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi);
this.mIconGenerator.setContentView(squareTextView);
mIconGenerator.setTextAppearance(com.google.maps.android.R.style.ClusterIcon_TextAppearance);
ShapeDrawable outline = new ShapeDrawable(new OvalShape());
outline.getPaint().setColor(Color.parseColor("#FFFFFF"));
LayerDrawable background = new LayerDrawable(new Drawable[]{outline, this.mColoredCircleBackground});
int strokeWidth = (int) (this.mDensity * 3.0F);
background.setLayerInset(1, strokeWidth, strokeWidth, strokeWidth, strokeWidth);
mIconGenerator.setBackground(background);
}
#Override
protected void onBeforeClusterRendered(Cluster<MyClusterItem> cluster, MarkerOptions markerOptions) {
int bucket = this.getBucket(cluster);
BitmapDescriptor descriptor = (BitmapDescriptor)this.mIcons.get(bucket);
if(descriptor == null) {
this.mColoredCircleBackground.getPaint().setColor(this.getColor(bucket));
descriptor = BitmapDescriptorFactory.fromBitmap(this.mIconGenerator.makeIcon(String.valueOf(cluster.getSize())));
this.mIcons.put(bucket, descriptor);
}
markerOptions.icon(descriptor);
}
}
on activity:
clusterManager.setRenderer(new MyClusterItemRenderer(getApplicationContext(), mGoogleMap, clusterManager));
Google Map by default provide approximate number of clustered items. To achieve exact number of clustered item, just override these two methods in your custom cluster renderer class:
#Override
protected String getClusterText(int bucket) {
return String.valueOf(bucket);
}
#Override
protected int getBucket(Cluster<MyItem> cluster) {
return cluster.getSize();
}
The complete code snippet is :
public class MarkerClusterRenderer extends DefaultClusterRenderer<MyItem> {
MarkerClusterRenderer(Context context, GoogleMap map, ClusterManager<MyItem> clusterManager) {
super(context, map, clusterManager);
}
#Override
protected void onBeforeClusterItemRendered(MyItem item, MarkerOptions markerOptions) {
// use this to make your change to the marker option
// for the marker before it gets render on the map
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_map_pins));
}
#Override
protected void onClusterItemRendered(MyItem item, Marker marker) {
//set marker title, tag, snippet, e.t.c here
}
#Override
protected String getClusterText(int bucket) {
return String.valueOf(bucket);
}
#Override
protected int getBucket(Cluster<MyItem> cluster) {
return cluster.getSize();
}
}
Here is the result :
Happy Coding :)
You can implement ClusterRenderer to customize the rendering of the clusters. DefaultClusterRenderer provides a good base to start from. By subclassing DefaultClusterRenderer, you can override the defaults.
For an in-depth example of customization, take a look at CustomMarkerClusteringDemoActivity in the demo app that ships with the utility library.
Source
This one is a bit old, but since it helped me find an easier solution I thought I post here.
Berdimurat has the right idea except the solution is simpler.
#Override
protected int getBucket(Cluster<FlagItemCluster> cluster) {
return cluster.getSize();
}
#Override
protected String getClusterText(int bucket) {
return String.valueOf(bucket);
}
Basically rather than using onBeforeClusterRendered, just alter where it gets it's data. This method also corrects that the number displayed isn't accurate because of what the getBucket logic contains.
Related
I try to cluster some points in my application map, It work prefect for first time and data clustered but when try to update the list again with new data not change happen and clusters still exist I try to use clearItems(), clear() map but no change happen the following is my code for it can any one help please.
Code for map
((SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map)).getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(final GoogleMap googleMap) {
mMap = googleMap;
LatLngBounds ADELAIDE = new LatLngBounds(
new LatLng(16.57946, 35.69014), new LatLng(31.67252, 50.20833));
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
final LatLng location = new LatLng(mDefaultLat, mDefaultLng);
mClusterManager = new ClusterManager<>(getContext(), mMap);
mClusterManager.setRenderer(new ClusterRenderer(getContext(), mMap, mClusterManager));
final CameraPosition[] mPreviousCameraPosition = {null};
mMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
CameraPosition position = googleMap.getCameraPosition();
mDefaultLat = position.target.latitude;
mDefaultLng = position.target.longitude;
locationFromMap = true;
populate();
if (mPreviousCameraPosition[0] == null || mPreviousCameraPosition[0].zoom != position.zoom) {
mPreviousCameraPosition[0] = googleMap.getCameraPosition();
addItems();
}
}
});
mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
#Override
public void onMapLoaded() {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(location, 16));
}
});
mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
try {
new OfficeDialog(mActivity, officeList.get(Integer.parseInt(marker.getSnippet()))).show();
} catch (Exception e) {
}
return true;
}
});
}
});
addItems function
private void addItems() {
if (!listAdded && officeList.size() > 0) {
mClusterManager.clearItems();
mClusterManager.cluster();
mClusterManager.addItems(officeList);
listAdded = true;
}
mClusterManager.cluster();
}
and my cluster manager custom render
public class ClusterRenderer extends DefaultClusterRenderer<Office> {
Context context;
private IconGenerator iconGenerator;
private float density;
public ClusterRenderer(Context context, GoogleMap map, ClusterManager<Office> clusterManager) {
super(context, map, clusterManager);
clusterManager.setRenderer(this);
this.context = context;
density = context.getResources().getDisplayMetrics().density;
}
#Override
protected void onBeforeClusterItemRendered(Office item, MarkerOptions markerOptions) {
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.ejar_location_icon_copy));
}
#Override
protected void onBeforeClusterRendered(Cluster<Office> cluster, MarkerOptions markerOptions) {
if(iconGenerator == null) {
iconGenerator = new IconGenerator(context);
iconGenerator.setContentView(makeTextView(context));
}
iconGenerator.setBackground(makeBackground(false, cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon(String.valueOf(cluster.getSize()))));
}
#Override
protected void onClusterRendered(Cluster<Office> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
}
#Override
protected boolean shouldRenderAsCluster(Cluster<Office> cluster) {
return cluster.getSize() > 1;
}
private ShapeDrawable makeBackground(boolean isClicked, int size) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
int color = ContextCompat.getColor(context, R.color.colorPrimary);
if (size < 50) {
color = ContextCompat.getColor(context, R.color.colorPrimary);
} else if (size < 100) {
color = ContextCompat.getColor(context, R.color.cluster_50);
} else if (size < 200) {
color = ContextCompat.getColor(context, R.color.cluster_100);
} else if (size < 1000) {
color = ContextCompat.getColor(context, R.color.cluster_200);
} else color = ContextCompat.getColor(context, R.color.cluster_1000);
background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
return background;
}
private SquareTextView makeTextView(Context context) {
SquareTextView squareTextView = new SquareTextView(context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(-2, -2);
squareTextView.setLayoutParams(layoutParams);
squareTextView.setTextColor(ContextCompat.getColor(context, R.color.white));
squareTextView.setId(com.google.maps.android.R.id.text);
int twelveDpi = (int) (12.0F * density);
squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi);
return squareTextView;
}
public IconGenerator getIconGenerator(boolean isClicked) {
iconGenerator.setBackground(makeBackground(isClicked, 0));
return iconGenerator;
}
}
After debug and search about this issue I didn't found any reason about it so after check my web service I found the issue on it.
Web service return static locations for my objects so the clusters not changed after fix it the application works correctly without any issues.
I have a map fragment that is handled by google's cluster manager that I configured with my custom cluster renderer extends DefaultClusterRenderer.
I have overritten the functions onBeforeClusterItemRendered and onBeforeClusterRendered to be able to diplay my pictures:
Now If the user zooms in it makes no sense to render the items that are not in the visible area.
It's very easy to find out if the item is in the visible area:
private Boolean isInBounds(LatLng position) {
return map.getProjection().getVisibleRegion().latLngBounds.contains(position);
}
But if I skip the rendering if the item is not currently visible, it will be empty when the user scrolls on the map.
So who knows how to get an event if the user scrolls and how to re render the items that are not in the visible bounds? (switchen from visible to non visible and vice versa)?
(Sorry for my bad English)
Here is my solution. It works very well and renders only the visible items.
I use the camera changed listener to re render the items that became visible now:
private void onBeforeClusterOrClusterItemRendered(final Cluster<MediaItem> cluster, final MediaItem mediaItem, final MarkerOptions markerOption
if(!isAdded())
return;
// In visible area?
Marker marker = cluster == null ? getMarker(mediaItem) : getMarker(cluster);
Boolean isInBounds = isInBounds(marker != null ? marker.getPosition() : mediaItem.getPosition(), null);
if(isInBounds) {
// ...
}
}
private Boolean isInBounds(LatLng position, LatLngBounds latLngBounds) {
return (latLngBounds == null ? mMap.getProjection().getVisibleRegion().latLngBounds : latLngBounds).contains(position);
}
#Override
protected void onBeforeClusterItemRendered(final MediaItem mediaItem, final MarkerOptions markerOptions) {
onBeforeClusterOrClusterItemRendered(null, mediaItem, markerOptions);
}
#Override
protected void onBeforeClusterRendered(final Cluster<MediaItem> cluster, final MarkerOptions markerOptions) {
final MediaItem mediaItem = MediaPicker.getBestRated(new ArrayList<>(cluster.getItems()));
onBeforeClusterOrClusterItemRendered(cluster, mediaItem, markerOptions);
}
...
// Re render
mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
mClusterManager.onCameraChange(cameraPosition);
final LatLngBounds latLngBounds = mMap.getProjection().getVisibleRegion().latLngBounds;
// Cluster only
Collection<Marker> clusters = mClusterManager.getClusterMarkerCollection().getMarkers();
for(Marker marker : clusters) {
if(isInBounds(marker.getPosition(), latLngBounds))
onBeforeClusterRendered(getCluster(marker), new MarkerOptions());
}
// Cluster item only
Collection<Marker> markers = mClusterManager.getMarkerCollection().getMarkers();
for(Marker marker : markers) {
if(isInBounds(marker.getPosition(), latLngBounds))
onBeforeClusterItemRendered(getClusterItem(marker), new MarkerOptions());
}
}
});
I had similar issue. I track initial location of the user and show map markers around him. If user chooses to look outside initial zoom, I download additional markers. Implementation:
map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
setCurrentRadius();
viewCenter = cameraPosition.target;
if (isNewGrounds(viewCenter, viewRadius)) {
downloadExtraPrinters(viewCenter,viewRadius);
}
myClusterManager.onCameraChange(cameraPosition);
}
});
private void setCurrentRadius() {
Projection projection = map.getProjection();
VisibleRegion currentView = projection.getVisibleRegion();
LatLng cameraCenter = map.getCameraPosition().target;
float[] projectionRadius = new float[1];
Location.distanceBetween(currentView.farLeft.latitude, currentView.farLeft.longitude, cameraCenter.latitude, cameraCenter.longitude, projectionRadius);
viewRadius = Math.round((projectionRadius[0] / 1000.0f) + 0.5f);
}
/** Method checks if camera goes outside bounds of previously downloaded area **/
protected boolean isNewGrounds(LatLng center, int radius) {
//Check if it is the first time to update
if (coveredGroundsCenter== null) {
coveredGroundsCenter=center;
coveredGroundsRadius= (int)(radius * EXTRA_RADIUS);
return true;
}else {
float[] centerDistance = new float[1];
Location.distanceBetween(coveredGroundsCenter.latitude, coveredGroundsCenter.longitude, center.latitude, center.longitude, centerDistance);
int criticalDistance = (int) Math.round((centerDistance[0] / 1000.0f) + 0.5f) + radius;
if (coveredGroundsRadius >= criticalDistance) {
return false;
} else {
coveredGroundsCenter = center;
coveredGroundsRadius = (int) (radius * EXTRA_RADIUS);
return true;
}
}
}
EXTRA_RADIUS is a constant I use to have a small margin outside visible map area, so the smallest camera movement wouldnt cause my map to download and re-render. I choosed this margin to be 50 percent of the current radius:
private static final double EXTRA_RADIUS=1.5;
private LatLng coveredGroundsCenter;
private int coveredGroundsRadius;
private LatLng viewCenter;
Maybe it's too late, but I hope I can help someone. I tried to find a solution, but I didn't succeed, so I started looking for a solution through clustering classes. I found a class called NonHierarchicalViewBasedAlgorithm but it didn't work properly or I used it incorrectly. And based on this class, I wrote my own using LatLngBounds. It's called VisibleBoundsAlgorithm.
Note: You must call the updateBounds() method before each clustering. Use this in onCameraIdleListener
Here is code:
VisibleBoundsAlgorithm.java
package com.example.demo.app;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm;
import com.google.maps.android.clustering.algo.ScreenBasedAlgorithm;
import com.google.maps.android.geometry.Bounds;
import com.google.maps.android.quadtree.PointQuadTree;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class VisibleBoundsAlgorithm<T extends ClusterItem>
extends NonHierarchicalDistanceBasedAlgorithm<T> implements ScreenBasedAlgorithm<T> {
private final GoogleMap map;
private LatLngBounds bounds;
public VisibleBoundsAlgorithm(GoogleMap map) {
this.map = map;
bounds = map.getProjection().getVisibleRegion().latLngBounds;
}
#Override
protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, float mapZoom) {
long oldMillis = System.currentTimeMillis(); //Getting time before operations
//Getting all items from QuadTree
List<QuadItem<T>> items = new ArrayList<>(quadTree.search(new Bounds(0, 1, 0, 1)));
ListIterator<QuadItem<T>> itemIterator = items.listIterator();
while (itemIterator.hasNext()) {
QuadItem<T> item = itemIterator.next();
if (!bounds.contains(item.getPosition())) { //if map do not contain item on this position remove it
itemIterator.remove();
}
}
long newMillis = System.currentTimeMillis();
Log.println(Log.ERROR, "Clustering items", "end in: " + (newMillis - oldMillis) + " (" + TimeUnit.SECONDS.convert(newMillis - oldMillis, TimeUnit.MILLISECONDS) + " seconds)");
return items; //Returning new massive of items
}
/**
* Call this before clustering.
*/
public void updateBounds() {
bounds = map.getProjection().getVisibleRegion().latLngBounds;
}
//When map is moving or zooming it should re-cluster
#Override
public boolean shouldReclusterOnMapMovement() {
return true;
}
#Override
public void onCameraChange(CameraPosition position) {
//required
}
}
In your Activity class:
ClusterManager<MyItem> cluster = new ClusterManager<MyItem>(context, map);
VisibleBoundsAlgorithm<MyItem> visibleBoundsAlgorithm = new VisibleBoundsAlgorithm<MyItem>(map);
cluster.setAlgorithm(visibleBoundsAlgorithm);
cluster.addItems(items);
cluster.cluster();
map.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
visibleBoundsAlgorithm.updateBounds();
cluster.cluster();
}
});
If someone have a question ask me.
I want to change the background of the cluster marker on click. I do this via
#Override
onClusterClick(Cluster<MyObject> cluster) {
Marker marker = renderer.getMarker(cluster);
marker.setIcon(....);
}
This works fine expect for one case: When I zoom in or out and the number of cluster markers doesn't change. For example, if I had a 15 cluster and a 5 cluster, then zoom a level in or out, the same two clusters remain. Clicking on one of these now renderer.getMarker(cluster) returns null. If they re-cluster after zooming, getMarker is not null.
My DefaultClusterRenderer is below. I checked the marker on onClusteredRendered and it's never null. Is this a bug in the DefaultClusterRenderer or should I do something else?
private class MyRenderer extends DefaultClusterRenderer<MyObject> {
private IconGenerator iconGenerator;
private float density;
public MyRenderer(Context context, GoogleMap map, ClusterManager<MyObject> clusterManager) {
super(context, map, clusterManager);
density = context.getResources().getDisplayMetrics().density;
}
#Override
protected void onBeforeClusterItemRendered(MyObject item, MarkerOptions markerOptions) {
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.my_pin));
}
#Override
protected void onBeforeClusterRendered(Cluster<MyObject> cluster, MarkerOptions markerOptions) {
if(iconGenerator == null) {
iconGenerator = new IconGenerator(getActivity());
iconGenerator.setContentView(makeTextView(getActivity()));
}
iconGenerator.setBackground(makeBackground(false));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon(String.valueOf(cluster.getSize()))));
}
#Override
protected void onClusterRendered(Cluster<MyObject> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
// Marker is never null here
}
#Override
protected boolean shouldRenderAsCluster(Cluster<MyObject> cluster) {
return cluster.getSize() > 1;
}
private ShapeDrawable makeBackground(boolean isClicked) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
background.setColorFilter(ContextCompat.getColor(getActivity(),
isClicked ? R.color.cluster_marker_clicked : R.color.cluster_marker_unclicked), PorterDuff.Mode.SRC_ATOP);
return background;
}
private SquareTextView makeTextView(Context context) {
SquareTextView squareTextView = new SquareTextView(context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(-2, -2);
squareTextView.setLayoutParams(layoutParams);
squareTextView.setTextColor(ContextCompat.getColor(getActivity(), R.color.white));
squareTextView.setTypeface(Utils.getFontBold(getActivity()));
squareTextView.setId(com.google.maps.android.R.id.text);
int twelveDpi = (int) (12.0F * density);
squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi);
return squareTextView;
}
public IconGenerator getIconGenerator(boolean isClicked) {
iconGenerator.setBackground(makeBackground(isClicked));
return iconGenerator;
}
}
Initializing the ClusterManager:
final ClusterManager<MyObject> mClusterManager = new ClusterManager<>(getActivity(), googleMap);
mClusterManager.addItems(items);
renderer = new CustomRenderer(getActivity(), googleMap, mClusterManager);
mClusterManager.setRenderer(renderer);
mClusterManager.cluster();
mClusterManager.setOnClusterItemClickListener(this);
googleMap.setOnMarkerClickListener(mClusterManager);
#antonio: This initialization is working for me:
public class MapsActivity extends FragmentActivity
implements ClusterManager.OnClusterClickListener<MyObject> {
// ...
private void setUpClusterer() {
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10));
mClusterManager = new ClusterManager<MyObject>(this, googleMap);
mClusterManager.setOnClusterClickListener(this);
renderer = new MyRenderer(this, googleMap, mClusterManager);
mClusterManager.setRenderer(renderer);
googleMap.setOnCameraChangeListener(mClusterManager);
googleMap.setOnMarkerClickListener(mClusterManager);
addItems();
}
private void addItems() {
// Set some lat/lng coordinates to start with.
double lat = 51.5145160;
double lng = -0.1270060;
// Add ten cluster items in close proximity, for purposes of this example.
for (int i = 0; i < 10; i++) {
double offset = i / 60d;
lat = lat + offset;
lng = lng + offset;
MyObject offsetItem = new MyObject(lat, lng);
mClusterManager.addItem(offsetItem);
}
}
#Override
public boolean onClusterClick(final Cluster<MyObject> cluster) {
Marker marker = renderer.getMarker(cluster);
marker.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.my_newpin));
return true;
}
}
Couldn't get renderer.getMarker(cluster) to return a marker in that case above. A work around was to create:
Map <Cluster<MyObject>, Marker> clusterMarkerMap = new HashMap<>();
then add them on the DefaultClusterRenderer callback because the marker is never null there:
#Override
protected void onClusterRendered(Cluster<MyObject> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
clusterMarkerMap.put(cluster, marker);
}
Because the DefaultClusterManager clears them when the camera position changes, wipe the cluster marker map before it creates new ones:
googleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
// Clear the map here because the markers will be recreated
// when the manager is notified of a (zoom level) camera change
if(zoomLevelChanged)
clusterMarkerMap.clear();
mClusterManager.onCameraChange(cameraPosition);
}
});
Now I can get the marker successfully clusterMarkerMap.get(cluster)
I want to chnage the color of marker that are not being clustered and shown as an individual marker on map.
I tried code from a demo project from android but I am not able get it working in my code.I change icon for one marker it chnaged everywhere and cluster disappears.
Here is the original code which I was trying to use
(Only relevant part from the code)
public class CustomMarkerClusteringDemoActivity extends BaseDemoActivity implements ClusterManager.OnClusterClickListener<Person>, ClusterManager.OnClusterInfoWindowClickListener<Person>, ClusterManager.OnClusterItemClickListener<Person>, ClusterManager.OnClusterItemInfoWindowClickListener<Person> {
private ClusterManager<Person> mClusterManager;
private Random mRandom = new Random(1984);
/**
* Draws profile photos inside markers (using IconGenerator).
* When there are multiple people in the cluster, draw multiple photos (using MultiDrawable).
*/
private class PersonRenderer extends DefaultClusterRenderer<Person> {
private final IconGenerator mIconGenerator = new IconGenerator(getApplicationContext());
private final IconGenerator mClusterIconGenerator = new IconGenerator(getApplicationContext());
private final ImageView mImageView;
private final ImageView mClusterImageView;
private final int mDimension;
public PersonRenderer() {
super(getApplicationContext(), getMap(), mClusterManager);
View multiProfile = getLayoutInflater().inflate(R.layout.multi_profile, null);
mClusterIconGenerator.setContentView(multiProfile);
mClusterImageView = (ImageView) multiProfile.findViewById(R.id.image);
mImageView = new ImageView(getApplicationContext());
mDimension = (int) getResources().getDimension(R.dimen.custom_profile_image);
mImageView.setLayoutParams(new ViewGroup.LayoutParams(mDimension, mDimension));
int padding = (int) getResources().getDimension(R.dimen.custom_profile_padding);
mImageView.setPadding(padding, padding, padding, padding);
mIconGenerator.setContentView(mImageView);
}
#Override
protected void onBeforeClusterItemRendered(Person person, MarkerOptions markerOptions) {
// Draw a single person.
// Set the info window to show their name.
mImageView.setImageResource(person.profilePhoto);
Bitmap icon = mIconGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(person.name);
}
#Override
protected void onBeforeClusterRendered(Cluster<Person> cluster, MarkerOptions markerOptions) {
// Draw multiple people.
// Note: this method runs on the UI thread. Don't spend too much time in here (like in this example).
List<Drawable> profilePhotos = new ArrayList<Drawable>(Math.min(4, cluster.getSize()));
int width = mDimension;
int height = mDimension;
for (Person p : cluster.getItems()) {
// Draw 4 at most.
if (profilePhotos.size() == 4) break;
Drawable drawable = getResources().getDrawable(p.profilePhoto);
drawable.setBounds(0, 0, width, height);
profilePhotos.add(drawable);
}
MultiDrawable multiDrawable = new MultiDrawable(profilePhotos);
multiDrawable.setBounds(0, 0, width, height);
mClusterImageView.setImageDrawable(multiDrawable);
Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}
Can anyone simplify or explain the process how this works
This is the code for onBeforeClusterRendered which I found after decompiling DefaultClusterRender class
protected void onBeforeClusterRendered(Cluster<T> cluster, MarkerOptions markerOptions) {
int bucket = this.getBucket(cluster);
BitmapDescriptor descriptor = (BitmapDescriptor)this.mIcons.get(bucket);
if(descriptor == null) {
this.mColoredCircleBackground.getPaint().setColor(this.getColor(bucket));
descriptor = BitmapDescriptorFactory.fromBitmap(this.mIconGenerator.makeIcon(this.getClusterText(bucket)));
this.mIcons.put(bucket, descriptor);
}
markerOptions.icon(descriptor);
}
But I can't understand a thing from this.
Is there any method to change the background color of the cluster item? (the one that displays the count of the markers, like 100+, 200+ ...). I tried to look into the source code of the ClusterManager but could not find any option to change the color, but maybe someone here knows how to do that. I basically want to "materialify" those colors a bit.
I was able to get a rough implementation working by using this demo from the library samples as a guide.
I used the lens icon from the Material Design Icons from here. After downloading the lens zip I put ic_lens_black_24dp.png under the drawable folder. Then I used the Drawable.setColorFilter() method to change the default color in the code.
I was also able to change the default Marker color, and figured I would include that as well here.
First, set a Renderer by calling setRenderer():
mClusterManager.setRenderer(new MyClusterRenderer(this, mMap,
mClusterManager));
Then, define the MyClusterRenderer class:
public class MyClusterRenderer extends DefaultClusterRenderer<MyItem> {
private final IconGenerator mClusterIconGenerator = new IconGenerator(getApplicationContext());
public MyClusterRenderer(Context context, GoogleMap map,
ClusterManager<MyItem> clusterManager) {
super(context, map, clusterManager);
}
#Override
protected void onBeforeClusterItemRendered(MyItem item,
MarkerOptions markerOptions) {
BitmapDescriptor markerDescriptor = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA);
markerOptions.icon(markerDescriptor);
}
#Override
protected void onClusterItemRendered(MyItem clusterItem, Marker marker) {
super.onClusterItemRendered(clusterItem, marker);
}
#Override
protected void onBeforeClusterRendered(Cluster<MyItem> cluster, MarkerOptions markerOptions){
final Drawable clusterIcon = getResources().getDrawable(R.drawable.ic_lens_black_24dp);
clusterIcon.setColorFilter(getResources().getColor(android.R.color.holo_orange_light), PorterDuff.Mode.SRC_ATOP);
mClusterIconGenerator.setBackground(clusterIcon);
//modify padding for one or two digit numbers
if (cluster.getSize() < 10) {
mClusterIconGenerator.setContentPadding(40, 20, 0, 0);
}
else {
mClusterIconGenerator.setContentPadding(30, 20, 0, 0);
}
Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}
}
Full class code:
public class MapsActivity extends AppCompatActivity
implements ClusterManager.OnClusterItemInfoWindowClickListener<MyItem> {
private ClusterManager<MyItem> mClusterManager;
private MyItem clickedClusterItem;
private GoogleMap mMap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
setUpMapIfNeeded();
}
#Override
protected void onResume() {
super.onResume();
setUpMapIfNeeded();
}
private void setUpMapIfNeeded() {
// Do a null check to confirm that we have not already instantiated the map.
if (mMap == null) {
// Try to obtain the map from the SupportMapFragment.
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
mMap.getUiSettings().setMapToolbarEnabled(true);
mMap.getUiSettings().setZoomControlsEnabled(true);
mMap.setMyLocationEnabled(true);
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
mClusterManager = new ClusterManager<>(this, mMap);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(37.779977,-122.413742), 10));
mMap.setOnCameraChangeListener(mClusterManager);
mMap.setOnMarkerClickListener(mClusterManager);
mClusterManager.setRenderer(new MyClusterRenderer(this, mMap,
mClusterManager));
mMap.setInfoWindowAdapter(mClusterManager.getMarkerManager());
mMap.setOnInfoWindowClickListener(mClusterManager); //added
mClusterManager.setOnClusterItemInfoWindowClickListener(this); //added
mClusterManager
.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<MyItem>() {
#Override
public boolean onClusterItemClick(MyItem item) {
clickedClusterItem = item;
return false;
}
});
addItems();
mClusterManager.getMarkerCollection().setOnInfoWindowAdapter(
new MyCustomAdapterForItems());
}
private void addItems() {
double latitude = 37.779977;
double longitude = -122.413742;
for (int i = 0; i < 10; i++) {
double offset = i / 60d;
double lat = latitude + offset;
double lng = longitude + offset;
MyItem offsetItem = new MyItem(lat, lng, "title " + i+1, "snippet " + i+1);
mClusterManager.addItem(offsetItem);
}
}
//added with edit
#Override
public void onClusterItemInfoWindowClick(MyItem myItem) {
//Cluster item InfoWindow clicked, set title as action
Intent i = new Intent(this, OtherActivity.class);
i.setAction(myItem.getTitle());
startActivity(i);
//You may want to do different things for each InfoWindow:
if (myItem.getTitle().equals("some title")){
//do something specific to this InfoWindow....
}
}
public class MyCustomAdapterForItems implements GoogleMap.InfoWindowAdapter {
private final View myContentsView;
MyCustomAdapterForItems() {
myContentsView = getLayoutInflater().inflate(
R.layout.info_window, null);
}
#Override
public View getInfoWindow(Marker marker) {
TextView tvTitle = ((TextView) myContentsView
.findViewById(R.id.txtTitle));
TextView tvSnippet = ((TextView) myContentsView
.findViewById(R.id.txtSnippet));
tvTitle.setText(clickedClusterItem.getTitle());
tvSnippet.setText(clickedClusterItem.getSnippet());
return myContentsView;
}
#Override
public View getInfoContents(Marker marker) {
return null;
}
}
public class MyClusterRenderer extends DefaultClusterRenderer<MyItem> {
private final IconGenerator mClusterIconGenerator = new IconGenerator(getApplicationContext());
public MyClusterRenderer(Context context, GoogleMap map,
ClusterManager<MyItem> clusterManager) {
super(context, map, clusterManager);
}
#Override
protected void onBeforeClusterItemRendered(MyItem item,
MarkerOptions markerOptions) {
BitmapDescriptor markerDescriptor = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA);
markerOptions.icon(markerDescriptor);
}
#Override
protected void onClusterItemRendered(MyItem clusterItem, Marker marker) {
super.onClusterItemRendered(clusterItem, marker);
}
#Override
protected void onBeforeClusterRendered(Cluster<MyItem> cluster, MarkerOptions markerOptions){
final Drawable clusterIcon = getResources().getDrawable(R.drawable.ic_lens_black_24dp);
clusterIcon.setColorFilter(getResources().getColor(android.R.color.holo_orange_light), PorterDuff.Mode.SRC_ATOP);
mClusterIconGenerator.setBackground(clusterIcon);
//modify padding for one or two digit numbers
if (cluster.getSize() < 10) {
mClusterIconGenerator.setContentPadding(40, 20, 0, 0);
}
else {
mClusterIconGenerator.setContentPadding(30, 20, 0, 0);
}
Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}
}
}
Result:
Initial app launch:
Zooming out, some clustering:
Zooming out again, all Markers clustered:
We can Override getColor in CustomClusterRenderer.
public class CustomClusterRenderer extends DefaultClusterRenderer<CustomClusterItem> {
#Override
protected int getColor(int clusterSize) {
return Color.parseColor("#567238");
}
}
I took some methods of superclass and partially remade them. Now i have beautiful standard clusters with my own colors.
public class CustomClusterRenderer extends DefaultClusterRenderer<GoogleMapMarker> {
private final IconGenerator mIconGenerator;
private ShapeDrawable mColoredCircleBackground;
private SparseArray<BitmapDescriptor> mIcons = new SparseArray();
private final float mDensity;
private Context mContext;
public CustomClusterRenderer(Context context, GoogleMap map,
ClusterManager<GoogleMapMarker> clusterManager) {
super(context, map, clusterManager);
this.mContext = context;
this.mDensity = context.getResources().getDisplayMetrics().density;
this.mIconGenerator = new IconGenerator(context);
this.mIconGenerator.setContentView(this.makeSquareTextView(context));
this.mIconGenerator.setTextAppearance(
com.google.maps.android.R.style.ClusterIcon_TextAppearance);
this.mIconGenerator.setBackground(this.makeClusterBackground());
}
#Override
protected void onBeforeClusterRendered(Cluster<GoogleMapMarker> cluster,
MarkerOptions markerOptions) {
// Main color
int clusterColor = mContext.getResources().getColor(R.color.colorPrimary);
int bucket = this.getBucket(cluster);
BitmapDescriptor descriptor = this.mIcons.get(bucket);
if(descriptor == null) {
this.mColoredCircleBackground.getPaint().setColor(clusterColor);
descriptor = BitmapDescriptorFactory.fromBitmap(
this.mIconGenerator.makeIcon(this.getClusterText(bucket)));
this.mIcons.put(bucket, descriptor);
}
markerOptions.icon(descriptor);
}
private SquareTextView makeSquareTextView(Context context) {
SquareTextView squareTextView = new SquareTextView(context);
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(-2, -2);
squareTextView.setLayoutParams(layoutParams);
squareTextView.setId(com.google.maps.android.R.id.text);
int twelveDpi = (int)(12.0F * this.mDensity);
squareTextView.setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi);
return squareTextView;
}
private LayerDrawable makeClusterBackground() {
// Outline color
int clusterOutlineColor = mContext.getResources().getColor(R.color.colorWhite);
this.mColoredCircleBackground = new ShapeDrawable(new OvalShape());
ShapeDrawable outline = new ShapeDrawable(new OvalShape());
outline.getPaint().setColor(clusterOutlineColor);
LayerDrawable background = new LayerDrawable(
new Drawable[]{outline, this.mColoredCircleBackground});
int strokeWidth = (int)(this.mDensity * 3.0F);
background.setLayerInset(1, strokeWidth, strokeWidth, strokeWidth, strokeWidth);
return background;
}
And then set renderer to Cluster Manager
mClusterManager = new ClusterManager<>(context, mGoogleMap);
mClusterManager.setRenderer(new CustomClusterRenderer(context, mGoogleMap, mClusterManager));
Nice custom renderer with centered text and different sizes of clusters:
public class MyClusterRenderer extends DefaultClusterRenderer<Station> {
private final IconGenerator mClusterIconGeneratorBig = new IconGenerator(getCtx());
private final IconGenerator mClusterIconGeneratorMed = new IconGenerator(getCtx());
private final IconGenerator mClusterIconGeneratorSml = new IconGenerator(getCtx());
final Drawable clusterIconBig = getResources().getDrawable(R.drawable.marker1);
final Drawable clusterIconMed = getResources().getDrawable(R.drawable.marker2);
final Drawable clusterIconSml = getResources().getDrawable(R.drawable.marker3);
public MyClusterRenderer(Context context, GoogleMap map,
ClusterManager<Station> clusterManager) {
super(context, map, clusterManager);
setupIconGen(mClusterIconGeneratorBig, clusterIconBig, context);
setupIconGen(mClusterIconGeneratorMed, clusterIconMed, context);
setupIconGen(mClusterIconGeneratorSml, clusterIconSml, context);
}
private void setupIconGen(IconGenerator generator, Drawable drawable, Context context) {
TextView textView = new TextView(context);
textView.setTextAppearance(context, R.style.BubbleText);
textView.setTypeface(App.FONTS[2]);
textView.setId(com.google.maps.android.R.id.amu_text);
textView.setGravity(android.view.Gravity.CENTER);
textView.setLayoutParams(new FrameLayout.LayoutParams(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()));
generator.setContentView(textView);
generator.setBackground(drawable);
}
#Override
protected void onBeforeClusterItemRendered(Station item, MarkerOptions markerOptions) {
BitmapDescriptor markerDescriptor = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA);
markerOptions.icon(markerDescriptor);
}
#Override
protected void onClusterItemRendered(Station clusterItem, Marker marker) {
super.onClusterItemRendered(clusterItem, marker);
}
#Override
protected void onBeforeClusterRendered(Cluster<Station> cluster, MarkerOptions markerOptions) {
if (cluster.getSize() > 20) {
Bitmap icon = mClusterIconGeneratorBig.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
} else if (cluster.getSize() > 10) {
Bitmap icon = mClusterIconGeneratorMed.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
} else {
Bitmap icon = mClusterIconGeneratorSml.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}
}
#Override
protected boolean shouldRenderAsCluster(Cluster cluster) {
return cluster.getSize() > 5;
}
}
Go to DefaultClusterRenderer (package com.google.maps.android.clustering.view;), and change the getColor() method to this:
private int getColor(int clusterSize) {
// custom color
double _logClusterSize; // log
final int _maxRed = Integer.parseInt("ff", 16);
// Log.v("kai", String.valueOf(_maxRed));
final int _minRed = Integer.parseInt("e6", 16);
final int _maxGreen = Integer.parseInt("a2", 16);
final int _minGreen = Integer.parseInt("47", 16);
final int _maxBlue = Integer.parseInt("93", 16);
final int _minBlue = Integer.parseInt("2d", 16);
final double _maxLogClusterSize = 10;
double _step = (_maxRed - _minRed) / _maxLogClusterSize;
_logClusterSize = Math.log(clusterSize);
if(_logClusterSize > 10) _logClusterSize = 10;
int _red = _maxRed - (int) (_step * _logClusterSize);
int _green = _maxGreen - (int) (_step * _logClusterSize);
int _blue = _maxBlue - (int) (_step * _logClusterSize);
return Color.rgb(_red, _green, _blue);
// 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
// });
}
This will change the Cluster color to pink, in the range of the color defined by min(max) red(green, blue). Hope that help!
Sadly, overriding getColor doesn't work for me. But this looks enough to change the marker color (and something else):
class ClusterItemRenderer(
context: Context, map: GoogleMap,
clusterManager: ClusterManager<ClusterMarker>
) : DefaultClusterRenderer<ClusterMarker>(context, map, clusterManager) {
override fun onBeforeClusterItemRendered(item: ClusterMarker, markerOptions: MarkerOptions) {
val markerDescriptor = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)
markerOptions.icon(markerDescriptor)
}
}
It's also possible to add updates according to the recommendations:
If you're using custom clustering (i.e, if you're extending
DefaultClusterRenderer), you must override two additional methods in
v1:
onClusterItemUpdated() - should be the same* as your onBeforeClusterItemRendered() method
onClusterUpdated() - should be the same* as your onBeforeClusterRendered() method
*Note that these methods can't be identical, as you need to use a Marker instead of MarkerOptions
override fun onClusterItemUpdated(item: ClusterMarker, marker: Marker) {
val markerDescriptor = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)
marker.setIcon(markerDescriptor)
}