Back to project page clusterkraf.
The source code is released under:
Apache License
If you think the Android project clusterkraf listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.twotoasters.clusterkraf; // w ww. j a va 2s .c o m import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import android.annotation.SuppressLint; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.view.View; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.CancelableCallback; import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener; import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; import com.google.android.gms.maps.Projection; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; /** * Clusters InputPoint objects on a GoogleMap */ public class Clusterkraf { private final WeakReference<GoogleMap> mapRef; private final Options options; private final ClusterTransitionsAnimation transitionsAnimation; private final InnerCallbackListener innerCallbackListener; private final ArrayList<InputPoint> points = new ArrayList<InputPoint>(); private ArrayList<ClusterPoint> currentClusters; private ArrayList<Marker> currentMarkers; private HashMap<Marker, ClusterPoint> currentClusterPointsByMarker = new HashMap<Marker, ClusterPoint>(); private ArrayList<ClusterPoint> previousClusters; private ArrayList<Marker> previousMarkers; private BaseClusteringTaskHost clusteringTaskHost; private ClusterTransitionsBuildingTaskHost clusterTransitionsBuildingTaskHost; /** * Construct a Clusterkraf instance to manage your map with customized * options and a list of points * * @param map * The GoogleMap to be managed by Clusterkraf * @param options * Customized options */ public Clusterkraf(GoogleMap map, Options options, ArrayList<InputPoint> points) { this.mapRef = new WeakReference<GoogleMap>(map); this.options = options; this.innerCallbackListener = new InnerCallbackListener(this); this.transitionsAnimation = new ClusterTransitionsAnimation(map, options, innerCallbackListener); if (points != null) { this.points.addAll(points); } if (map != null) { map.setOnCameraChangeListener(innerCallbackListener.clusteringOnCameraChangeListener); map.setOnMarkerClickListener(innerCallbackListener); map.setOnInfoWindowClickListener(innerCallbackListener); map.setInfoWindowAdapter(innerCallbackListener); } showAllClusters(); } /** * Add a single InputPoint for clustering * * @param inputPoint * The InputPoint object to be clustered */ public void add(InputPoint inputPoint) { // @TODO: test individually adding points if (inputPoint != null) { points.add(inputPoint); updateClustersAndTransition(); } } /** * Add a list of InputPoint objects for clustering * * @param inputPoints * The list of InputPoint objects for clustering */ public void addAll(ArrayList<InputPoint> inputPoints) { if (inputPoints != null) { points.addAll(inputPoints); updateClustersAndTransition(); } } /** * Remove all existing InputPoint objects and add a new list of InputPoint * objects for clustering * * @param inputPoints * The new list of InputPoint objects for clustering */ public void replace(ArrayList<InputPoint> inputPoints) { clear(); addAll(inputPoints); } /** * Remove all Clusterkraf-managed markers from the map */ public void clear() { /** * cancel the background thread clustering task */ if (clusteringTaskHost != null) { clusteringTaskHost.cancel(); clusteringTaskHost = null; } /** * cancel the background thread transition building task */ if (clusterTransitionsBuildingTaskHost != null) { clusterTransitionsBuildingTaskHost.cancel(); clusterTransitionsBuildingTaskHost = null; } /** * we avoid GoogleMap.clear() so users can manage their own * non-clustered markers on the map. * * @see http://code.google.com/p/gmaps-api-issues/issues/detail?id=4703 */ if (currentMarkers != null) { for (Marker marker : currentMarkers) { marker.remove(); } } currentClusters = null; currentClusterPointsByMarker = null; currentMarkers = null; previousClusters = null; previousMarkers = null; points.clear(); } // TODO: support removing individual InputPoint objects private void drawMarkers() { GoogleMap map = mapRef.get(); if (map != null && currentClusters != null) { currentMarkers = new ArrayList<Marker>(currentClusters.size()); currentClusterPointsByMarker = new HashMap<Marker, ClusterPoint>(currentClusters.size()); MarkerOptionsChooser moc = options.getMarkerOptionsChooser(); for (ClusterPoint clusterPoint : currentClusters) { MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(clusterPoint.getMapPosition()); if (moc != null) { moc.choose(markerOptions, clusterPoint); } Marker marker = map.addMarker(markerOptions); currentMarkers.add(marker); currentClusterPointsByMarker.put(marker, clusterPoint); } } } private void removePreviousMarkers() { GoogleMap map = mapRef.get(); if (map != null && previousClusters != null && previousMarkers != null) { for (Marker marker : previousMarkers) { marker.remove(); } previousMarkers = null; previousClusters = null; } } private void updateClustersAndTransition() { previousClusters = currentClusters; previousMarkers = currentMarkers; startClusteringTask(); } private void startClusteringTask() { clusteringTaskHost = new UpdateClustersAndTransitionClusteringTaskHost(); clusteringTaskHost.executeTask(); } private void transitionClusters(ClusterTransitions clusterTransitions) { if (clusterTransitions != null) { transitionsAnimation.animate(clusterTransitions); } clusterTransitionsBuildingTaskHost = null; } private void startClusterTransitionsBuildingTask(Projection projection) { clusterTransitionsBuildingTaskHost = new ClusterTransitionsBuildingTaskHost(); clusterTransitionsBuildingTaskHost.executeTask(projection); clusteringTaskHost = null; } private void showAllClusters() { if (clusteringTaskHost != null) { drawMarkers(); clusteringTaskHost = null; } else { clusteringTaskHost = new ShowAllClustersClusteringTaskHost(); clusteringTaskHost.executeTask(); } } /** * Animate the camera so all of the InputPoint objects represented by the * passed ClusterPoint are in view * * @param clusterPoint */ public void zoomToBounds(ClusterPoint clusterPoint) { GoogleMap map = mapRef.get(); if (map != null && clusterPoint != null) { innerCallbackListener.clusteringOnCameraChangeListener.setDirty(System.currentTimeMillis()); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(clusterPoint.getBoundsOfInputPoints(), options.getZoomToBoundsPadding()); map.animateCamera(cameraUpdate, options.getZoomToBoundsAnimationDuration(), null); } } /** * Show the InfoWindow for the passed Marker and ClusterPoint * * @param marker * @param clusterPoint */ public void showInfoWindow(Marker marker, ClusterPoint clusterPoint) { GoogleMap map = mapRef.get(); if (map != null && marker != null && clusterPoint != null) { long dirtyUntil = System.currentTimeMillis() + options.getShowInfoWindowAnimationDuration(); innerCallbackListener.clusteringOnCameraChangeListener.setDirty(dirtyUntil); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(marker.getPosition()); map.animateCamera(cameraUpdate, options.getShowInfoWindowAnimationDuration(), new CancelableCallback() { @Override public void onFinish() { innerCallbackListener.handler.post(new Runnable() { @Override public void run() { innerCallbackListener.clusteringOnCameraChangeListener.setDirty(0); } }); } @Override public void onCancel() { innerCallbackListener.clusteringOnCameraChangeListener.setDirty(0); } }); marker.showInfoWindow(); } } private static class InnerCallbackListener implements ClusteringOnCameraChangeListener.Host, ClusterTransitionsAnimation.Host, OnMarkerClickListener, OnInfoWindowClickListener, GoogleMap.InfoWindowAdapter { private final WeakReference<Clusterkraf> clusterkrafRef; private final Handler handler = new Handler(); private InnerCallbackListener(Clusterkraf clusterkraf) { clusterkrafRef = new WeakReference<Clusterkraf>(clusterkraf); clusteringOnCameraChangeListener = new ClusteringOnCameraChangeListener(this, clusterkraf.options); } private final ClusteringOnCameraChangeListener clusteringOnCameraChangeListener; @Override public void onClusteringCameraChange() { Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { if (clusterkraf.clusteringTaskHost != null) { clusterkraf.clusteringTaskHost.cancel(); clusterkraf.clusteringTaskHost = null; } if (clusterkraf.clusterTransitionsBuildingTaskHost != null) { clusterkraf.clusterTransitionsBuildingTaskHost.cancel(); clusterkraf.clusterTransitionsBuildingTaskHost = null; } clusterkraf.transitionsAnimation.cancel(); clusterkraf.updateClustersAndTransition(); } } /** * @see com.twotoasters.clusterkraf.ClusterTransitionsAnimation.Host# * onClusterTransitionStarting() */ @Override public void onClusterTransitionStarting() { clusteringOnCameraChangeListener.setDirty(System.currentTimeMillis()); } /** * @see com.twotoasters.clusterkraf.ClusterTransitionsAnimation.Host# * onClusterTransitionStarted() */ @Override public void onClusterTransitionStarted() { Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { /** * now that the first frame of the transition has been drawn, we * can remove our previous markers without suffering any * blinking markers */ clusterkraf.removePreviousMarkers(); } } /** * @see com.twotoasters.clusterkraf.ClusterTransitionsAnimation.Host# * onClusterTransitionFinished() */ @Override public void onClusterTransitionFinished() { Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { clusterkraf.drawMarkers(); /** * now that we have drawn our new set of markers, we can let the * transitionsAnimation know so it can clear its markers */ clusterkraf.transitionsAnimation.onHostPlottedDestinationClusterPoints(); } clusteringOnCameraChangeListener.setDirty(0); } /** * @see com.google.android.gms.maps.GoogleMap.OnMarkerClickListener#onMarkerClick * (com.google.android.gms.maps.model.Marker) */ @Override public boolean onMarkerClick(Marker marker) { boolean handled = false; boolean exempt = false; Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker); if (clusterPoint == null) { if (clusterkraf.transitionsAnimation.getAnimatedDestinationClusterPoint(marker) != null) { exempt = true; // animated marker click is not supported } else { clusterPoint = clusterkraf.transitionsAnimation.getStationaryClusterPoint(marker); } } OnMarkerClickDownstreamListener downstreamListener = clusterkraf.options.getOnMarkerClickDownstreamListener(); if (exempt == false && downstreamListener != null) { handled = downstreamListener.onMarkerClick(marker, clusterPoint); } if (exempt == false && handled == false && clusterPoint != null) { if (clusterPoint.size() > 1) { switch(clusterkraf.options.getClusterClickBehavior()) { case ZOOM_TO_BOUNDS: clusterkraf.zoomToBounds(clusterPoint); handled = true; break; case SHOW_INFO_WINDOW: clusterkraf.showInfoWindow(marker, clusterPoint); handled = true; break; case NO_OP: // no-op break; } } else { switch(clusterkraf.options.getSinglePointClickBehavior()) { case SHOW_INFO_WINDOW: clusterkraf.showInfoWindow(marker, clusterPoint); handled = true; break; case NO_OP: // no-op break; } } } } return handled || exempt; } /** * @see com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener# * onInfoWindowClick(com.google.android.gms.maps.model.Marker) */ @Override public void onInfoWindowClick(Marker marker) { Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { boolean handled = false; ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker); OnInfoWindowClickDownstreamListener downstreamListener = clusterkraf.options.getOnInfoWindowClickDownstreamListener(); if (downstreamListener != null) { handled = downstreamListener.onInfoWindowClick(marker, clusterPoint); } if (handled == false && clusterPoint != null) { if (clusterPoint.size() > 1) { switch(clusterkraf.options.getClusterInfoWindowClickBehavior()) { case ZOOM_TO_BOUNDS: clusterkraf.zoomToBounds(clusterPoint); break; case HIDE_INFO_WINDOW: marker.hideInfoWindow(); break; case NO_OP: // no-op break; } } else { switch(clusterkraf.options.getSinglePointInfoWindowClickBehavior()) { case HIDE_INFO_WINDOW: marker.hideInfoWindow(); break; case NO_OP: // no-op break; } } } } } /** * @see com.google.android.gms.maps.GoogleMap.InfoWindowAdapter * getInfoWindow(com.google.android.gms.maps.model.Marker) */ @Override public View getInfoWindow(Marker marker) { View infoWindow = null; Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker); InfoWindowDownstreamAdapter infoWindowDownstreamAdapter = clusterkraf.options.getInfoWindowDownstreamAdapter(); if (infoWindowDownstreamAdapter != null) { infoWindow = infoWindowDownstreamAdapter.getInfoWindow(marker, clusterPoint); } } return infoWindow; // Google Map will handle it when null } /** * @see com.google.android.gms.maps.GoogleMap.InfoWindowAdapter * getInfoContents(com.google.android.gms.maps.model.Marker) */ @Override public View getInfoContents(Marker marker) { View infoWindow = null; Clusterkraf clusterkraf = clusterkrafRef.get(); if (clusterkraf != null) { ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker); InfoWindowDownstreamAdapter infoWindowDownstreamAdapter = clusterkraf.options.getInfoWindowDownstreamAdapter(); if (infoWindowDownstreamAdapter != null) { infoWindow = infoWindowDownstreamAdapter.getInfoContents(marker, clusterPoint); } } return infoWindow; // Google Map will handle it when null } } abstract private class BaseClusteringTaskHost implements ClusteringTask.Host { private ClusteringTask task; BaseClusteringTaskHost() { this.task = new ClusteringTask(this); } @Override public void onClusteringTaskPostExecute(ClusteringTask.Result result) { currentClusters = result.currentClusters; onCurrentClustersSet(result); task = null; } public void cancel() { ProcessingListener processingListener = options.getProcessingListener(); if (processingListener != null) { processingListener.onClusteringFinished(); } task.cancel(true); task = null; } @SuppressLint("NewApi") public void executeTask() { GoogleMap map = mapRef.get(); if (map != null) { ProcessingListener processingListener = options.getProcessingListener(); if (processingListener != null) { processingListener.onClusteringStarted(); } ClusteringTask.Argument arg = new ClusteringTask.Argument(); arg.projection = map.getProjection(); arg.options = options; arg.points = points; arg.previousClusters = previousClusters; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, arg); } else { task.execute(arg); } } } abstract protected void onCurrentClustersSet(ClusteringTask.Result result); } private class ShowAllClustersClusteringTaskHost extends BaseClusteringTaskHost { @Override protected void onCurrentClustersSet(ClusteringTask.Result result) { ProcessingListener processingListener = options.getProcessingListener(); if (processingListener != null) { processingListener.onClusteringFinished(); } showAllClusters(); } } private class UpdateClustersAndTransitionClusteringTaskHost extends BaseClusteringTaskHost { @Override protected void onCurrentClustersSet(ClusteringTask.Result result) { startClusterTransitionsBuildingTask(result.projection); } } private class ClusterTransitionsBuildingTaskHost implements ClusterTransitionsBuildingTask.Host { private ClusterTransitionsBuildingTask task; ClusterTransitionsBuildingTaskHost() { this.task = new ClusterTransitionsBuildingTask(this); } @Override public void onClusterTransitionsBuildingTaskPostExecute(ClusterTransitionsBuildingTask.Result result) { ProcessingListener processingListener = options.getProcessingListener(); if (processingListener != null) { processingListener.onClusteringFinished(); } if (result != null) { transitionClusters(result.clusterTransitions); } task = null; } public void cancel() { task.cancel(true); task = null; } @SuppressLint("NewApi") public void executeTask(Projection projection) { if (projection != null) { ClusterTransitionsBuildingTask.Argument arg = new ClusterTransitionsBuildingTask.Argument(); arg.currentClusters = currentClusters; arg.previousClusters = previousClusters; arg.projection = projection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, arg); } else { task.execute(arg); } } } } public interface ProcessingListener { void onClusteringStarted(); void onClusteringFinished(); } }