Wednesday, April 11, 2018

Android - Google map cluster utility

Introduction:  Google map is a great way to show the user current location in the world and some time we need to show more than one image at a location. The cluster is nothing more than a group of markers combined together.

To achieve it in Android google provide us dependencies that we have to add in the application grade file.
compile 'com.google.android.gms:play-services-maps:9.0.2'
compile 'com.google.maps.android:android-maps-utils:0.4.3'

Here, i'm going to explain a clustering utility that i have used in one of my projects.  So, for use it you have to provide an instance of google map and screen context in the constructor of utility and call init() method for initial setup. And call addUser() method with locations of the item.
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.crashlytics.android.Crashlytics;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.view.DefaultClusterRenderer;
import com.google.maps.android.ui.IconGenerator;
import java.util.List;
public class GoogleMapClusterUtil implements ClusterManager.OnClusterClickListener <GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterInfoWindowClickListener<GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterItemClickListener<GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterItemInfoWindowClickListener<GoogleMapClusterUtil.Person> {
    private final Context mContext;
    private final GoogleMap mGoogleMap;
    private ClusterManager<Person> mClusterManager;
    public GoogleMapClusterUtil(Context context, GoogleMap googleMap) {
        mContext = context;
        mGoogleMap = googleMap;
    }
    /**
     * Create instance of cluster.
     */
    public void init() {
        mClusterManager = new ClusterManager<>(mContext, mGoogleMap);
        mClusterManager.setRenderer(new PersonRenderer());
        mGoogleMap.setOnMarkerClickListener(mClusterManager);
        mGoogleMap.setOnInfoWindowClickListener(mClusterManager);
        mClusterManager.setOnClusterClickListener(this);
        mClusterManager.setOnClusterInfoWindowClickListener(this);
        mClusterManager.setOnClusterItemClickListener(this);
        mClusterManager.setOnClusterItemInfoWindowClickListener(this);
        mClusterManager.cluster();
}
 
    /**
     * Update cluster on camera idle.
     */
    public void onCameraIdle() {
        mClusterManager.onCameraIdle();
    }
    public void addUser(List<Location> userNearMe) {
        clearAllItem();
        for (Location appUserLocation : userNearMe) {
            // the address is blank for now, will be used later
            mClusterManager.addItem(new Person(new LatLng(appUserLocation.getLatitude(), appUserLocation.getLongitude()), 0));
        }
    }
  
    /**
     * Delete all cluster from map.
     */
    public void clearAllItem() {
        mClusterManager.clearItems();
    }
    @Override
    public boolean onClusterClick(Cluster<Person> cluster) {
      
        // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items
        // inside of bounds, then animate to center of the bounds.
        // Create the builder to collect all essential cluster items for the bounds.
        LatLngBounds.Builder builder = LatLngBounds.builder();
        for (ClusterItem item : cluster.getItems()) {
            builder.include(item.getPosition());
        }
        // Get the LatLngBounds
        final LatLngBounds bounds = builder.build();
        // Animate camera to the bounds
        try {
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
        } catch (Exception e) {
            Crashlytics.logException(e);
        }
        return true;
    }
    @Override
    public void onClusterInfoWindowClick(Cluster<Person> cluster) {
        // onClusterInfoWindowClick
    }
    @Override
    public boolean onClusterItemClick(Person person) {
     
        return false;
    }
    @Override
    public void onClusterItemInfoWindowClick(Person person) {
        // onClusterItemInfoWindowClick
    }
   
    public class Person implements ClusterItem {
        public final int id;
        private final LatLng mPosition;
        private Person(LatLng position, int id) {
            this.id = id;
            mPosition = position;
        }
        public int getId() {
            return id;
        }
        @Override
        public LatLng getPosition() {
            return mPosition;
        }
        @Override
        public String getTitle() {
            return null;
        }
        @Override
        public String getSnippet() {
            return null;
        }
    }
    /**
     * Manage cluster update.
     */
    private class PersonRenderer extends DefaultClusterRenderer<Person> {
        private PersonRenderer() {
            super(mContext, mGoogleMap, mClusterManager);
        }
        @Override
        protected void onBeforeClusterItemRendered(Person person, MarkerOptions markerOptions) {
            // Draw a single person.
            // Set the info window to show their name.
            if (person.getId()>0){
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.location_package));
            }else{
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.location));
            }
        }
        @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).
            IconGenerator iconGenerator = new IconGenerator(mContext);
            iconGenerator.setBackground(null);
            LayoutInflater myInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View activityView = myInflater.inflate(R.layout.item_cluster, null, false);
            activityView.findViewById(R.id.item_cluster_iv_cluster_icon).setBackgroundResource(R.drawable.location_blank);
            if (cluster.getSize() > 99) {
                ((TextView) activityView.findViewById(R.id.amu_text)).setText(String.valueOf("99+"));
            } else {
                ((TextView) activityView.findViewById(R.id.amu_text)).setText(String.valueOf(cluster.getSize()));
            }
            iconGenerator.setContentView(activityView);
            BitmapDescriptor icon = BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon());
            markerOptions.icon(icon);
        }
        @Override
        protected boolean shouldRenderAsCluster(Cluster cluster) {
            // Always render clusters.
            return cluster.getSize() > 1;
        }
    }
}


for display cluster google map i have used following XML file. You can change it according to your requirement.
layout: item_cluster
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="30dp"
             android:layout_height="30dp"
             android:padding="2dp">
    <ImageView
        android:id="@+id/item_cluster_iv_cluster_icon"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@null"/>
    <TextView
        android:id="@+id/amu_text"
        android:layout_width="wrap_parent"
        android:layout_height="wrap_parent"
        android:layout_gravity="center"
        android:gravity="center"
        android:paddingBottom="2dp"
        />
</FrameLayout>



At the end build it in your project. You will see, after double click on the map. it will expand item.


 




Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...