@SuppressLint("NewApi") public void run() { if (clusters.equals(DefaultClusterRenderer.this.mClusters)) { mCallback.run(); return; } final MarkerModifier markerModifier = new MarkerModifier(); final float zoom = mMapZoom; final boolean zoomingIn = zoom > mZoom; final float zoomDelta = zoom - mZoom; final Set<MarkerWithPosition> markersToRemove = mMarkers; final LatLngBounds visibleBounds = mProjection.getVisibleRegion().latLngBounds; // TODO: Add some padding, so that markers can animate in from off-screen. // Find all of the existing clusters that are on-screen. These are candidates for // markers to animate from. List<Point> existingClustersOnScreen = null; if (DefaultClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) { existingClustersOnScreen = new ArrayList<Point>(); for (Cluster<T> c : DefaultClusterRenderer.this.mClusters) { if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) { Point point = mSphericalMercatorProjection.toPoint(c.getPosition()); existingClustersOnScreen.add(point); } } } // Create the new markers and animate them to their new positions. final Set<MarkerWithPosition> newMarkers = new HashSet<MarkerWithPosition>(); for (Cluster<T> c : clusters) { boolean onScreen = visibleBounds.contains(c.getPosition()); if (zoomingIn && onScreen && SHOULD_ANIMATE) { Point point = mSphericalMercatorProjection.toPoint(c.getPosition()); Point closest = findClosestCluster(existingClustersOnScreen, point); if (closest != null) { LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest); markerModifier.add(true, new CreateMarkerTask(c, newMarkers, animateTo)); } else { markerModifier.add(true, new CreateMarkerTask(c, newMarkers, null)); } } else { markerModifier.add(onScreen, new CreateMarkerTask(c, newMarkers, null)); } } // Wait for all markers to be added. markerModifier.waitUntilFree(); // Don't remove any markers that were just added. This is basically anything that had // a hit in the MarkerCache. markersToRemove.removeAll(newMarkers); // Find all of the new clusters that were added on-screen. These are candidates for // markers to animate from. List<Point> newClustersOnScreen = null; if (SHOULD_ANIMATE) { newClustersOnScreen = new ArrayList<Point>(); for (Cluster<T> c : clusters) { if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) { Point p = mSphericalMercatorProjection.toPoint(c.getPosition()); newClustersOnScreen.add(p); } } } // Remove the old markers, animating them into clusters if zooming out. for (final MarkerWithPosition marker : markersToRemove) { boolean onScreen = visibleBounds.contains(marker.position); // Don't animate when zooming out more than 3 zoom levels. // TODO: drop animation based on speed of device & number of markers to animate. if (!zoomingIn && zoomDelta > -3 && onScreen && SHOULD_ANIMATE) { final Point point = mSphericalMercatorProjection.toPoint(marker.position); final Point closest = findClosestCluster(newClustersOnScreen, point); if (closest != null) { LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest); markerModifier.animateThenRemove(marker, marker.position, animateTo); } else { markerModifier.remove(true, marker.marker); } } else { markerModifier.remove(onScreen, marker.marker); } } markerModifier.waitUntilFree(); mMarkers = newMarkers; DefaultClusterRenderer.this.mClusters = clusters; mZoom = zoom; mCallback.run(); }