Android Maps Utils Clustering show InfoWindow
Here is a simplified and slightly modified solution based on this answer. Note that the linked answer implements an InfoWindow for both Markers and Clusters.
This solution only implements InfoWindows for Markers.
It's similar to how you would implement a custom InfoWindowAdapter for normal Markers with no Clustering, but with the additional requirement that you keep a reference to the currently selected Item so that you can get the Title and Snippet from it's MyItem
instance, since the Marker does not store the Title and Snippet as it usually does.
Note that since all of the data is stored in MyItem
references, it's much easier to extend the functionality to display as many data types as you want in the InfoWindow for each Marker.
First, the MyItem.java, which includes extra fields for Title and Snippet:
public class MyItem implements ClusterItem { private final LatLng mPosition; private final String mTitle; private final String mSnippet; public MyItem(double lat, double lng, String t, String s) { mPosition = new LatLng(lat, lng); mTitle = t; mSnippet = s; } @Override public LatLng getPosition() { return mPosition; } public String getTitle(){ return mTitle; } public String getSnippet(){ return mSnippet; }}
Here is the full Activity class, which includes all of the functionality to support InfoWindows for each Marker added using the Cluster library:
Edit: Added support for handling click events on the InfoWindow, made the Activity implement OnClusterItemInfoWindowClickListener
and added the onClusterItemInfoWindowClick
callback.
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); 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; } }}
info_window.xml:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="20dp" android:orientation="vertical" android:background="#000000"> <TextView android:id="@+id/txtTitle" android:textColor="#D3649F" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/txtSnippet" android:textColor="#D3649F" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
Result:
Initial launch:
Zooming out, starts Clustering:
Zooming out again, more Clustering:
Then, zooming in, and clicking on an individual Marker:
Then clicking on another Marker:
Edit: In order to show the "speech bubble" around the custom InfoWindow, use getInfoContents()
instead of getInfoWindow()
:
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) { return null; } @Override public View getInfoContents(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; }}
Result:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.map); mClusterManager = new ClusterManager<>(this, mMap); mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); mMap.setOnCameraChangeListener(mClusterManager); mMap.setInfoWindowAdapter(new InfoWindowAdapter() { /** * View for displaying marker popup, if null default framework view would be used */ @Override public View getInfoWindow(Marker marker) { return null; } /** * For changing the content of infowindow * Called when showMarkerInfo method is called */ @Override public View getInfoContents(Marker marker) { View v = getLayoutInflater().inflate(R.layout.view_to_inflate, null); //code for initializing view part return v; } }); readItems(); }
You could consider the following approach:
public void initilizeMap() { googleMap = mFragment.getMap(); googleMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN); googleMap.getUiSettings().setZoomControlsEnabled(true`enter code here`); // true to`enter code here` googleMap.getUiSettings().setZoomGesturesEnabled(true); googleMap.getUiSettings().setCompassEnabled(true); googleMap.getUiSettings().setMyLocationButtonEnabled(true); googleMap.getUiSettings().setRotateGesturesEnabled(true); if (googleMap == null) { Toast.makeText(getActivity(), "Sorry! unable to create maps", Toast.LENGTH_SHORT).show(); } mClusterManager = new ClusterManager<MyItem>(getActivity(), googleMap );// googleMap.setInfoWindowAdapter(new CustomInfoWindowAdapter()); googleMap.setOnMapLoadedCallback(this); googleMap.setMyLocationEnabled(true); googleMap.setBuildingsEnabled(true); googleMap.getUiSettings().setTiltGesturesEnabled(true);MyItem offsetItem = new MyItem(Double.parseDouble(outletList.get(i).getMap_latitude()), Double.parseDouble(outletList.get(i).getMap_longitude()), title , address); mClusterManager.addItem(offsetItem); googleMap.setInfoWindowAdapter(new CustomInfoWindowAdapter(offsetItem));} private class CustomInfoWindowAdapter implements InfoWindowAdapter { Marker marker; private View view; private MyItem items; public CustomInfoWindowAdapter(MyItem item) { view = getActivity().getLayoutInflater().inflate( R.layout.custom_info_window, null); this.items = item; } @Override public View getInfoContents(Marker marker) { if (marker != null && marker.isInfoWindowShown()) { marker.hideInfoWindow(); marker.showInfoWindow(); } return null; } @Override public View getInfoWindow(final Marker marker) { this.marker = marker; String url = null; if (marker.getId() != null && markers != null && markers.size() > 0) { if (markers.get(marker.getId()) != null && markers.get(marker.getId()) != null) { url = markers.get(marker.getId()); } } final ImageView image = ((ImageView) view.findViewById(R.id.badge)); if (url != null && !url.equalsIgnoreCase("null") && !url.equalsIgnoreCase("")) { imageLoader.displayImage(url, image, options, new SimpleImageLoadingListener() { @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); getInfoContents(marker); } }); } else { image.setImageResource(R.drawable.ic_launcher); } final String title = items.getTitle(); Log.e(TAG, "TITLE : "+title); final TextView titleUi = ((TextView) view.findViewById(R.id.title)); if (title != null) { titleUi.setText(title); } else { titleUi.setText(""); } final String address = items.getAddress(); final TextView snippetUi = ((TextView) view .findViewById(R.id.snippet)); if (address != null) { snippetUi.setText(address); } else { snippetUi.setText(""); }