diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java')
-rw-r--r-- | platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java | 1089 |
1 files changed, 545 insertions, 544 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index 8989bb22d7..d953bfca0c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -32,609 +32,610 @@ import java.util.Map; */ public class MarkerViewManager implements MapView.OnMapChangedListener { - private final ViewGroup markerViewContainer; - private final Map<MarkerView, View> markerViewMap = new HashMap<>(); - private final LongSparseArray<OnMarkerViewAddedListener> markerViewAddedListenerMap = new LongSparseArray<>(); - private final List<MapboxMap.MarkerViewAdapter> markerViewAdapters = new ArrayList<>(); - - // TODO refactor MapboxMap out for Projection and Transform - // Requires removing MapboxMap from Annotations by using Peer model from #6912 - private MapboxMap mapboxMap; - - private long viewMarkerBoundsUpdateTime; - private MapboxMap.OnMarkerViewClickListener onMarkerViewClickListener; - private boolean isWaitingForRenderInvoke; - - /** - * Creates an instance of MarkerViewManager. - * - * @param container the ViewGroup associated with the MarkerViewManager - */ - public MarkerViewManager(@NonNull ViewGroup container) { - this.markerViewContainer = container; - this.markerViewAdapters.add(new ImageMarkerViewAdapter(container.getContext())); + private final ViewGroup markerViewContainer; + private final Map<MarkerView, View> markerViewMap = new HashMap<>(); + private final LongSparseArray<OnMarkerViewAddedListener> markerViewAddedListenerMap = new LongSparseArray<>(); + private final List<MapboxMap.MarkerViewAdapter> markerViewAdapters = new ArrayList<>(); + + // TODO refactor MapboxMap out for Projection and Transform + // Requires removing MapboxMap from Annotations by using Peer model from #6912 + private MapboxMap mapboxMap; + + private long viewMarkerBoundsUpdateTime; + private MapboxMap.OnMarkerViewClickListener onMarkerViewClickListener; + private boolean isWaitingForRenderInvoke; + + /** + * Creates an instance of MarkerViewManager. + * + * @param container the ViewGroup associated with the MarkerViewManager + */ + public MarkerViewManager(@NonNull ViewGroup container) { + this.markerViewContainer = container; + this.markerViewAdapters.add(new ImageMarkerViewAdapter(container.getContext())); + } + + // TODO refactor MapboxMap out for Projection and Transform + // Requires removing MapboxMap from Annotations by using Peer model from #6912 + public void bind(MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + } + + + @Override + public void onMapChanged(@MapView.MapChange int change) { + if (isWaitingForRenderInvoke && change == MapView.DID_FINISH_RENDERING_FRAME_FULLY_RENDERED) { + isWaitingForRenderInvoke = false; + invalidateViewMarkersInVisibleRegion(); } - - // TODO refactor MapboxMap out for Projection and Transform - // Requires removing MapboxMap from Annotations by using Peer model from #6912 - public void bind(MapboxMap mapboxMap) { - this.mapboxMap = mapboxMap; + } + + public void setWaitingForRenderInvoke(boolean waitingForRenderInvoke) { + isWaitingForRenderInvoke = waitingForRenderInvoke; + } + + /** + * Animate a MarkerView to a given rotation. + * <p> + * The {@link MarkerView} will be rotated from its current rotation to the given rotation. + * </p> + * + * @param marker the MarkerView to rotate. + * @param rotation the rotation value. + */ + public void animateRotation(@NonNull MarkerView marker, float rotation) { + View convertView = markerViewMap.get(marker); + if (convertView != null) { + AnimatorUtils.rotate(convertView, rotation); } - - - @Override - public void onMapChanged(@MapView.MapChange int change) { - if (isWaitingForRenderInvoke && change == MapView.DID_FINISH_RENDERING_FRAME_FULLY_RENDERED) { - isWaitingForRenderInvoke = false; - invalidateViewMarkersInVisibleRegion(); - } + } + + /** + * Animate a MarkerView with a given rotation. + * + * @param marker the MarkerView to rotate by. + * @param rotation the rotation by value, limited to 0 - 360 degrees. + */ + public void animateRotationBy(@NonNull MarkerView marker, float rotation) { + View convertView = markerViewMap.get(marker); + if (convertView != null) { + convertView.animate().cancel(); + // calculate new direction + float diff = rotation - convertView.getRotation(); + if (diff > 180.0f) { + diff -= 360.0f; + } else if (diff < -180.0f) { + diff += 360.f; + } + AnimatorUtils.rotateBy(convertView, diff); } - - public void setWaitingForRenderInvoke(boolean waitingForRenderInvoke) { - isWaitingForRenderInvoke = waitingForRenderInvoke; + } + + /** + * Animate a MarkerView to a given alpha value. + * <p> + * The {@link MarkerView} will be transformed from its current alpha value to the given value. + * </p> + * + * @param marker the MarkerView to change its alpha value. + * @param alpha the alpha value. + */ + public void animateAlpha(@NonNull MarkerView marker, float alpha) { + View convertView = markerViewMap.get(marker); + if (convertView != null) { + AnimatorUtils.alpha(convertView, alpha); } - - /** - * Animate a MarkerView to a given rotation. - * <p> - * The {@link MarkerView} will be rotated from its current rotation to the given rotation. - * </p> - * - * @param marker the MarkerView to rotate. - * @param rotation the rotation value. - */ - public void animateRotation(@NonNull MarkerView marker, float rotation) { - View convertView = markerViewMap.get(marker); - if (convertView != null) { - AnimatorUtils.rotate(convertView, rotation); - } + } + + /** + * Animate a MarkerVIew to be visible or invisible + * <p> + * The {@link MarkerView} will be made {@link View#VISIBLE} or {@link View#GONE}. + * </p> + * + * @param marker the MarkerView to change its visibility + * @param visible the flag indicating if MarkerView is visible + */ + public void animateVisible(@NonNull MarkerView marker, boolean visible) { + View convertView = markerViewMap.get(marker); + if (convertView != null) { + convertView.setVisibility(visible ? View.VISIBLE : View.GONE); } - - /** - * Animate a MarkerView with a given rotation. - * - * @param marker the MarkerView to rotate by. - * @param rotation the rotation by value, limited to 0 - 360 degrees. - */ - public void animateRotationBy(@NonNull MarkerView marker, float rotation) { - View convertView = markerViewMap.get(marker); - if (convertView != null) { - convertView.animate().cancel(); - // calculate new direction - float diff = rotation - convertView.getRotation(); - if (diff > 180.0f) { - diff -= 360.0f; - } else if (diff < -180.0f) { - diff += 360.f; + } + + /** + * Updates the position of MarkerViews currently found in the viewport. + * <p> + * The collection of {@link MarkerView} will be iterated and each item position will be updated. + * If an item is View state is not visible and its related flag is set to visible, the + * {@link MarkerView} will be animated to visible using alpha animation. + * </p> + */ + public void update() { + for (final MarkerView marker : markerViewMap.keySet()) { + final View convertView = markerViewMap.get(marker); + if (convertView != null) { + PointF point = mapboxMap.getProjection().toScreenLocation(marker.getPosition()); + if (marker.getOffsetX() == MapboxConstants.UNMEASURED) { + // ensure view is measured first + if (marker.getWidth() == 0) { + convertView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + if (convertView.getMeasuredWidth() != 0) { + marker.setWidth(convertView.getMeasuredWidth()); + marker.setHeight(convertView.getMeasuredHeight()); } - AnimatorUtils.rotateBy(convertView, diff); + } + } + if (marker.getWidth() != 0) { + int x = (int) (marker.getAnchorU() * marker.getWidth()); + int y = (int) (marker.getAnchorV() * marker.getHeight()); + marker.setOffset(x, y); } - } - /** - * Animate a MarkerView to a given alpha value. - * <p> - * The {@link MarkerView} will be transformed from its current alpha value to the given value. - * </p> - * - * @param marker the MarkerView to change its alpha value. - * @param alpha the alpha value. - */ - public void animateAlpha(@NonNull MarkerView marker, float alpha) { - View convertView = markerViewMap.get(marker); - if (convertView != null) { - AnimatorUtils.alpha(convertView, alpha); + convertView.setX(point.x - marker.getOffsetX()); + convertView.setY(point.y - marker.getOffsetY()); + + // animate visibility + if (marker.isVisible() && convertView.getVisibility() == View.GONE) { + animateVisible(marker, true); } + } } - - /** - * Animate a MarkerVIew to be visible or invisible - * <p> - * The {@link MarkerView} will be made {@link View#VISIBLE} or {@link View#GONE}. - * </p> - * - * @param marker the MarkerView to change its visibility - * @param visible the flag indicating if MarkerView is visible - */ - public void animateVisible(@NonNull MarkerView marker, boolean visible) { - View convertView = markerViewMap.get(marker); + } + + /** + * Set tilt on every non flat MarkerView currently shown in the Viewport. + * + * @param tilt the tilt value. + */ + public void setTilt(float tilt) { + View convertView; + for (MarkerView markerView : markerViewMap.keySet()) { + if (markerView.isFlat()) { + convertView = markerViewMap.get(markerView); if (convertView != null) { - convertView.setVisibility(visible ? View.VISIBLE : View.GONE); + markerView.setTilt(tilt); + convertView.setRotationX(tilt); } + } } - - /** - * Updates the position of MarkerViews currently found in the viewport. - * <p> - * The collection of {@link MarkerView} will be iterated and each item position will be updated. - * If an item is View state is not visible and its related flag is set to visible, the - * {@link MarkerView} will be animated to visible using alpha animation. - * </p> - */ - public void update() { - for (final MarkerView marker : markerViewMap.keySet()) { - final View convertView = markerViewMap.get(marker); - if (convertView != null) { - PointF point = mapboxMap.getProjection().toScreenLocation(marker.getPosition()); - if (marker.getOffsetX() == MapboxConstants.UNMEASURED) { - // ensure view is measured first - if (marker.getWidth() == 0) { - convertView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - if (convertView.getMeasuredWidth() != 0) { - marker.setWidth(convertView.getMeasuredWidth()); - marker.setHeight(convertView.getMeasuredHeight()); - } - } - } - if (marker.getWidth() != 0) { - int x = (int) (marker.getAnchorU() * marker.getWidth()); - int y = (int) (marker.getAnchorV() * marker.getHeight()); - marker.setOffset(x, y); - } - - convertView.setX(point.x - marker.getOffsetX()); - convertView.setY(point.y - marker.getOffsetY()); - - // animate visibility - if (marker.isVisible() && convertView.getVisibility() == View.GONE) { - animateVisible(marker, true); - } - } - } + } + + /** + * Update and invalidate the MarkerView icon. + * + * @param markerView the marker view to updates. + */ + public void updateIcon(@NonNull MarkerView markerView) { + View convertView = markerViewMap.get(markerView); + if (convertView != null && convertView instanceof ImageView) { + ((ImageView) convertView).setImageBitmap(markerView.getIcon().getBitmap()); } - - /** - * Set tilt on every non flat MarkerView currently shown in the Viewport. - * - * @param tilt the tilt value. - */ - public void setTilt(float tilt) { - View convertView; - for (MarkerView markerView : markerViewMap.keySet()) { - if (markerView.isFlat()) { - convertView = markerViewMap.get(markerView); - if (convertView != null) { - markerView.setTilt(tilt); - convertView.setRotationX(tilt); - } - } + } + + /** + * Animate a MarkerView to a deselected state. + * <p> + * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onDeselect(MarkerView, View)} + * will be called to execute an animation. + * </p> + * + * @param marker the MarkerView to deselect. + */ + public void deselect(@NonNull MarkerView marker) { + deselect(marker, true); + } + + /** + * Animate a MarkerView to a deselected state. + * <p> + * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onDeselect(MarkerView, View)} + * will be called to execute an animation. + * </p> + * + * @param marker the MarkerView to deselect. + * @param callbackToMap indicates if deselect marker must be called on MapboxMap. + */ + public void deselect(@NonNull MarkerView marker, boolean callbackToMap) { + final View convertView = markerViewMap.get(marker); + if (convertView != null) { + for (MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { + if (adapter.getMarkerClass().equals(marker.getClass())) { + adapter.onDeselect(marker, convertView); } + } } - - /** - * Update and invalidate the MarkerView icon. - * - * @param markerView the marker view to updates. - */ - public void updateIcon(@NonNull MarkerView markerView) { - View convertView = markerViewMap.get(markerView); - if (convertView != null && convertView instanceof ImageView) { - ((ImageView) convertView).setImageBitmap(markerView.getIcon().getBitmap()); - } + if (callbackToMap) { + mapboxMap.deselectMarker(marker); } - - /** - * Animate a MarkerView to a deselected state. - * <p> - * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onDeselect(MarkerView, View)} - * will be called to execute an animation. - * </p> - * - * @param marker the MarkerView to deselect. - */ - public void deselect(@NonNull MarkerView marker) { - deselect(marker, true); + marker.setSelected(false); + } + + /** + * Animate a MarkerView to a selected state. + * + * @param marker the MarkerView object to select. + */ + public void select(@NonNull MarkerView marker) { + select(marker, true); + } + + /** + * Animate a MarkerView to a selected state. + * + * @param marker the MarkerView object to select. + * @param callbackToMap indicates if select marker must be called on {@link MapboxMap}. + */ + public void select(@NonNull MarkerView marker, boolean callbackToMap) { + final View convertView = markerViewMap.get(marker); + for (MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { + if (adapter.getMarkerClass().equals(marker.getClass())) { + select(marker, convertView, adapter, callbackToMap); + } } - - /** - * Animate a MarkerView to a deselected state. - * <p> - * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onDeselect(MarkerView, View)} - * will be called to execute an animation. - * </p> - * - * @param marker the MarkerView to deselect. - * @param callbackToMap indicates if deselect marker must be called on MapboxMap. - */ - public void deselect(@NonNull MarkerView marker, boolean callbackToMap) { - final View convertView = markerViewMap.get(marker); - if (convertView != null) { - for (MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { - if (adapter.getMarkerClass().equals(marker.getClass())) { - adapter.onDeselect(marker, convertView); - } - } - } + } + + /** + * Animate a MarkerView to a selected state. + * <p> + * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onSelect(MarkerView, View, boolean)} + * will be called to execute an animation. + * </p> + * + * @param marker the MarkerView object to select. + * @param convertView the View presentation of the MarkerView. + * @param adapter the adapter used to adapt the marker to the convertView. + */ + public void select(@NonNull MarkerView marker, View convertView, MapboxMap.MarkerViewAdapter adapter) { + select(marker, convertView, adapter, true); + } + + + /** + * Animate a MarkerView to a selected state. + * <p> + * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onSelect(MarkerView, View, boolean)} + * will be called to execute an animation. + * </p> + * + * @param marker the MarkerView object to select. + * @param convertView the View presentation of the MarkerView. + * @param adapter the adapter used to adapt the marker to the convertView. + * @param callbackToMap indicates if select marker must be called on MapboxMap. + */ + public void select(@NonNull MarkerView marker, View convertView, MapboxMap.MarkerViewAdapter adapter, + boolean callbackToMap) { + if (convertView != null) { + if (adapter.onSelect(marker, convertView, false)) { if (callbackToMap) { - mapboxMap.deselectMarker(marker); + mapboxMap.selectMarker(marker); } - marker.setSelected(false); + } + marker.setSelected(true); + convertView.bringToFront(); } - - /** - * Animate a MarkerView to a selected state. - * - * @param marker the MarkerView object to select. - */ - public void select(@NonNull MarkerView marker) { - select(marker, true); + } + + /** + * Get view representation from a MarkerView. If marker is not found in current viewport, + * {@code null} is returned. + * + * @param marker the marker to get the view. + * @return the Android SDK View object. + */ + @Nullable + public View getView(MarkerView marker) { + return markerViewMap.get(marker); + } + + /** + * Get the view adapter for a marker. + * + * @param markerView the marker to get the view adapter. + * @return the MarkerView adapter. + */ + @Nullable + public MapboxMap.MarkerViewAdapter getViewAdapter(MarkerView markerView) { + MapboxMap.MarkerViewAdapter adapter = null; + for (MapboxMap.MarkerViewAdapter a : markerViewAdapters) { + if (a.getMarkerClass().equals(markerView.getClass())) { + adapter = a; + } } - - /** - * Animate a MarkerView to a selected state. - * - * @param marker the MarkerView object to select. - * @param callbackToMap indicates if select marker must be called on {@link MapboxMap}. - */ - public void select(@NonNull MarkerView marker, boolean callbackToMap) { - final View convertView = markerViewMap.get(marker); - for (MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { - if (adapter.getMarkerClass().equals(marker.getClass())) { - select(marker, convertView, adapter, callbackToMap); - } + return adapter; + } + + /** + * Remove a MarkerView from a map. + * <p> + * The {@link MarkerView} will be removed using an alpha animation and related {@link View} + * will be released to the android.support.v4.util.Pools.SimplePool from the related + * {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter}. It's possible to remove + * the {@link MarkerView} from the underlying collection if needed. + * </p> + * + * @param marker the MarkerView to remove. + */ + public void removeMarkerView(MarkerView marker) { + final View viewHolder = markerViewMap.get(marker); + if (viewHolder != null && marker != null) { + for (final MapboxMap.MarkerViewAdapter<?> adapter : markerViewAdapters) { + if (adapter.getMarkerClass().equals(marker.getClass())) { + if (adapter.prepareViewForReuse(marker, viewHolder)) { + // reset offset for reuse + marker.setOffset(MapboxConstants.UNMEASURED, MapboxConstants.UNMEASURED); + adapter.releaseView(viewHolder); + } } + } } - - /** - * Animate a MarkerView to a selected state. - * <p> - * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onSelect(MarkerView, View, boolean)} - * will be called to execute an animation. - * </p> - * - * @param marker the MarkerView object to select. - * @param convertView the View presentation of the MarkerView. - * @param adapter the adapter used to adapt the marker to the convertView. - */ - public void select(@NonNull MarkerView marker, View convertView, MapboxMap.MarkerViewAdapter adapter) { - select(marker, convertView, adapter, true); + marker.setMapboxMap(null); + markerViewMap.remove(marker); + } + + /** + * Add a MarkerViewAdapter to the MarkerViewManager. + * <p> + * The provided MarkerViewAdapter must supply a generic subclass of MarkerView. + * </p> + * + * @param markerViewAdapter the MarkerViewAdapter to add. + */ + public void addMarkerViewAdapter(MapboxMap.MarkerViewAdapter markerViewAdapter) { + if (markerViewAdapter.getMarkerClass().equals(MarkerView.class)) { + throw new RuntimeException("Providing a custom MarkerViewAdapter requires subclassing MarkerView"); } - - /** - * Animate a MarkerView to a selected state. - * <p> - * The {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter#onSelect(MarkerView, View, boolean)} - * will be called to execute an animation. - * </p> - * - * @param marker the MarkerView object to select. - * @param convertView the View presentation of the MarkerView. - * @param adapter the adapter used to adapt the marker to the convertView. - * @param callbackToMap indicates if select marker must be called on MapboxMap. - */ - public void select(@NonNull MarkerView marker, View convertView, MapboxMap.MarkerViewAdapter adapter, boolean callbackToMap) { - if (convertView != null) { - if (adapter.onSelect(marker, convertView, false)) { - if (callbackToMap) { - mapboxMap.selectMarker(marker); - } - } - marker.setSelected(true); - convertView.bringToFront(); - } + if (!markerViewAdapters.contains(markerViewAdapter)) { + markerViewAdapters.add(markerViewAdapter); + invalidateViewMarkersInVisibleRegion(); } - - /** - * Get view representation from a MarkerView. If marker is not found in current viewport, - * {@code null} is returned. - * - * @param marker the marker to get the view. - * @return the Android SDK View object. - */ - @Nullable - public View getView(MarkerView marker) { - return markerViewMap.get(marker); + } + + /** + * Get all MarkerViewAdapters associated with this MarkerViewManager. + * + * @return a List of MarkerViewAdapters. + */ + public List<MapboxMap.MarkerViewAdapter> getMarkerViewAdapters() { + return markerViewAdapters; + } + + /** + * Register a callback to be invoked when this view is clicked. + * + * @param listener the callback to be invoked. + */ + public void setOnMarkerViewClickListener(@Nullable MapboxMap.OnMarkerViewClickListener listener) { + onMarkerViewClickListener = listener; + } + + /** + * Schedule that ViewMarkers found in the viewport are invalidated. + * <p> + * This method is rate limited, and {@link #invalidateViewMarkersInVisibleRegion} will only be called + * once each 250 ms. + * </p> + */ + public void scheduleViewMarkerInvalidation() { + if (!markerViewAdapters.isEmpty()) { + long currentTime = SystemClock.elapsedRealtime(); + if (currentTime < viewMarkerBoundsUpdateTime) { + return; + } + invalidateViewMarkersInVisibleRegion(); + viewMarkerBoundsUpdateTime = currentTime + 250; } - - /** - * Get the view adapter for a marker. - * - * @param markerView the marker to get the view adapter. - * @return the MarkerView adapter. - */ - @Nullable - public MapboxMap.MarkerViewAdapter getViewAdapter(MarkerView markerView) { - MapboxMap.MarkerViewAdapter adapter = null; - for (MapboxMap.MarkerViewAdapter a : markerViewAdapters) { - if (a.getMarkerClass().equals(markerView.getClass())) { - adapter = a; - } + } + + /** + * Invalidate the ViewMarkers found in the viewport. + * <p> + * This method will remove any markers that aren't in the viewport anymore and will add new + * ones for each found Marker in the changed viewport. + * </p> + */ + public void invalidateViewMarkersInVisibleRegion() { + RectF mapViewRect = new RectF(0, 0, markerViewContainer.getWidth(), markerViewContainer.getHeight()); + List<MarkerView> markers = mapboxMap.getMarkerViewsInRect(mapViewRect); + View convertView; + + // remove old markers + Iterator<MarkerView> iterator = markerViewMap.keySet().iterator(); + while (iterator.hasNext()) { + MarkerView marker = iterator.next(); + if (!markers.contains(marker)) { + // remove marker + convertView = markerViewMap.get(marker); + for (MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { + if (adapter.getMarkerClass().equals(marker.getClass())) { + adapter.prepareViewForReuse(marker, convertView); + adapter.releaseView(convertView); + marker.setMapboxMap(null); + iterator.remove(); + } } - return adapter; + } } - /** - * Remove a MarkerView from a map. - * <p> - * The {@link MarkerView} will be removed using an alpha animation and related {@link View} - * will be released to the android.support.v4.util.Pools.SimplePool from the related - * {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter}. It's possible to remove - * the {@link MarkerView} from the underlying collection if needed. - * </p> - * - * @param marker the MarkerView to remove. - */ - public void removeMarkerView(MarkerView marker) { - final View viewHolder = markerViewMap.get(marker); - if (viewHolder != null && marker != null) { - for (final MapboxMap.MarkerViewAdapter<?> adapter : markerViewAdapters) { - if (adapter.getMarkerClass().equals(marker.getClass())) { - if (adapter.prepareViewForReuse(marker, viewHolder)) { - // reset offset for reuse - marker.setOffset(MapboxConstants.UNMEASURED, MapboxConstants.UNMEASURED); - adapter.releaseView(viewHolder); - } + // introduce new markers + for (final MarkerView marker : markers) { + if (!markerViewMap.containsKey(marker)) { + for (final MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { + if (adapter.getMarkerClass().equals(marker.getClass())) { + + // Inflate View + convertView = (View) adapter.getViewReusePool().acquire(); + final View adaptedView = adapter.getView(marker, convertView, markerViewContainer); + if (adaptedView != null) { + adaptedView.setRotationX(marker.getTilt()); + adaptedView.setRotation(marker.getRotation()); + adaptedView.setAlpha(marker.getAlpha()); + adaptedView.setVisibility(View.GONE); + + if (mapboxMap.getSelectedMarkers().contains(marker)) { + // if a marker to be shown was selected + // replay that animation with duration 0 + if (adapter.onSelect(marker, adaptedView, true)) { + mapboxMap.selectMarker(marker); } + } + + marker.setMapboxMap(mapboxMap); + markerViewMap.put(marker, adaptedView); + if (convertView == null) { + adaptedView.setVisibility(View.GONE); + markerViewContainer.addView(adaptedView); + } } - } - marker.setMapboxMap(null); - markerViewMap.remove(marker); - } - /** - * Add a MarkerViewAdapter to the MarkerViewManager. - * <p> - * The provided MarkerViewAdapter must supply a generic subclass of MarkerView. - * </p> - * - * @param markerViewAdapter the MarkerViewAdapter to add. - */ - public void addMarkerViewAdapter(MapboxMap.MarkerViewAdapter markerViewAdapter) { - if (markerViewAdapter.getMarkerClass().equals(MarkerView.class)) { - throw new RuntimeException("Providing a custom MarkerViewAdapter requires subclassing MarkerView"); - } - - if (!markerViewAdapters.contains(markerViewAdapter)) { - markerViewAdapters.add(markerViewAdapter); - invalidateViewMarkersInVisibleRegion(); + // notify listener is marker view is rendered + OnMarkerViewAddedListener onViewAddedListener = markerViewAddedListenerMap.get(marker.getId()); + if (onViewAddedListener != null) { + onViewAddedListener.onViewAdded(marker); + markerViewAddedListenerMap.remove(marker.getId()); + } + } } + } } - /** - * Get all MarkerViewAdapters associated with this MarkerViewManager. - * - * @return a List of MarkerViewAdapters. - */ - public List<MapboxMap.MarkerViewAdapter> getMarkerViewAdapters() { - return markerViewAdapters; + // clear map, don't keep references to MarkerView listeners that are not found in the bounds of the map. + markerViewAddedListenerMap.clear(); + + // trigger update to make newly added ViewMarker visible, + // these would only be updated when the map is moved. + update(); + } + + /** + * When the provided {@link MarkerView} is clicked on by a user, we check if a custom click + * event has been created and if not, display a {@link InfoWindow}. + * + * @param markerView that the click event occurred. + */ + public boolean onClickMarkerView(MarkerView markerView) { + boolean clickHandled = false; + + MapboxMap.MarkerViewAdapter adapter = getViewAdapter(markerView); + View view = getView(markerView); + if (adapter == null || view == null) { + // not a valid state + return true; } - /** - * Register a callback to be invoked when this view is clicked. - * - * @param listener the callback to be invoked. - */ - public void setOnMarkerViewClickListener(@Nullable MapboxMap.OnMarkerViewClickListener listener) { - onMarkerViewClickListener = listener; + if (onMarkerViewClickListener != null) { + clickHandled = onMarkerViewClickListener.onMarkerClick(markerView, view, adapter); } - /** - * Schedule that ViewMarkers found in the viewport are invalidated. - * <p> - * This method is rate limited, and {@link #invalidateViewMarkersInVisibleRegion} will only be called - * once each 250 ms. - * </p> - */ - public void scheduleViewMarkerInvalidation() { - if (!markerViewAdapters.isEmpty()) { - long currentTime = SystemClock.elapsedRealtime(); - if (currentTime < viewMarkerBoundsUpdateTime) { - return; - } - invalidateViewMarkersInVisibleRegion(); - viewMarkerBoundsUpdateTime = currentTime + 250; - } + if (!clickHandled) { + ensureInfoWindowOffset(markerView); + select(markerView, view, adapter); } - /** - * Invalidate the ViewMarkers found in the viewport. - * <p> - * This method will remove any markers that aren't in the viewport anymore and will add new - * ones for each found Marker in the changed viewport. - * </p> - */ - public void invalidateViewMarkersInVisibleRegion() { - RectF mapViewRect = new RectF(0, 0, markerViewContainer.getWidth(), markerViewContainer.getHeight()); - List<MarkerView> markers = mapboxMap.getMarkerViewsInRect(mapViewRect); - View convertView; - - // remove old markers - Iterator<MarkerView> iterator = markerViewMap.keySet().iterator(); - while (iterator.hasNext()) { - MarkerView marker = iterator.next(); - if (!markers.contains(marker)) { - // remove marker - convertView = markerViewMap.get(marker); - for (MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { - if (adapter.getMarkerClass().equals(marker.getClass())) { - adapter.prepareViewForReuse(marker, convertView); - adapter.releaseView(convertView); - marker.setMapboxMap(null); - iterator.remove(); - } - } - } - } - - // introduce new markers - for (final MarkerView marker : markers) { - if (!markerViewMap.containsKey(marker)) { - for (final MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { - if (adapter.getMarkerClass().equals(marker.getClass())) { - - // Inflate View - convertView = (View) adapter.getViewReusePool().acquire(); - final View adaptedView = adapter.getView(marker, convertView, markerViewContainer); - if (adaptedView != null) { - adaptedView.setRotationX(marker.getTilt()); - adaptedView.setRotation(marker.getRotation()); - adaptedView.setAlpha(marker.getAlpha()); - adaptedView.setVisibility(View.GONE); - - if (mapboxMap.getSelectedMarkers().contains(marker)) { - // if a marker to be shown was selected - // replay that animation with duration 0 - if (adapter.onSelect(marker, adaptedView, true)) { - mapboxMap.selectMarker(marker); - } - } - - marker.setMapboxMap(mapboxMap); - markerViewMap.put(marker, adaptedView); - if (convertView == null) { - adaptedView.setVisibility(View.GONE); - markerViewContainer.addView(adaptedView); - } - } - - // notify listener is marker view is rendered - OnMarkerViewAddedListener onViewAddedListener = markerViewAddedListenerMap.get(marker.getId()); - if (onViewAddedListener != null) { - onViewAddedListener.onViewAdded(marker); - markerViewAddedListenerMap.remove(marker.getId()); - } - } - } - } + return clickHandled; + } + + /** + * Handles the {@link MarkerView}'s info window offset. + * + * @param marker that we are ensuring info window offset. + */ + public void ensureInfoWindowOffset(MarkerView marker) { + View view = null; + if (markerViewMap.containsKey(marker)) { + view = markerViewMap.get(marker); + } else { + for (final MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { + if (adapter.getMarkerClass().equals(marker.getClass())) { + View convertView = (View) adapter.getViewReusePool().acquire(); + view = adapter.getView(marker, convertView, markerViewContainer); + break; } - - // clear map, don't keep references to MarkerView listeners that are not found in the bounds of the map. - markerViewAddedListenerMap.clear(); - - // trigger update to make newly added ViewMarker visible, - // these would only be updated when the map is moved. - update(); + } } - /** - * When the provided {@link MarkerView} is clicked on by a user, we check if a custom click - * event has been created and if not, display a {@link InfoWindow}. - * - * @param markerView that the click event occurred. - */ - public boolean onClickMarkerView(MarkerView markerView) { - boolean clickHandled = false; - - MapboxMap.MarkerViewAdapter adapter = getViewAdapter(markerView); - View view = getView(markerView); - if (adapter == null || view == null) { - // not a valid state - return true; + if (view != null) { + if (marker.getWidth() == 0) { + if (view.getMeasuredWidth() == 0) { + //Ensure the marker's view is measured first + view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); } - - if (onMarkerViewClickListener != null) { - clickHandled = onMarkerViewClickListener.onMarkerClick(markerView, view, adapter); - } - - if (!clickHandled) { - ensureInfoWindowOffset(markerView); - select(markerView, view, adapter); - } - - return clickHandled; + marker.setWidth(view.getMeasuredWidth()); + marker.setHeight(view.getMeasuredHeight()); + } + + // update position on map + if (marker.getOffsetX() == MapboxConstants.UNMEASURED) { + int x = (int) (marker.getAnchorU() * marker.getWidth()); + int y = (int) (marker.getAnchorV() * marker.getHeight()); + marker.setOffset(x, y); + } + + // InfoWindow offset + int infoWindowOffsetX = (int) ((view.getMeasuredWidth() * marker.getInfoWindowAnchorU()) - marker.getOffsetX()); + int infoWindowOffsetY = (int) ((view.getMeasuredHeight() * marker.getInfoWindowAnchorV()) - marker.getOffsetY()); + marker.setTopOffsetPixels(infoWindowOffsetY); + marker.setRightOffsetPixels(infoWindowOffsetX); } + } - /** - * Handles the {@link MarkerView}'s info window offset. - * - * @param marker that we are ensuring info window offset. - */ - public void ensureInfoWindowOffset(MarkerView marker) { - View view = null; - if (markerViewMap.containsKey(marker)) { - view = markerViewMap.get(marker); - } else { - for (final MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { - if (adapter.getMarkerClass().equals(marker.getClass())) { - View convertView = (View) adapter.getViewReusePool().acquire(); - view = adapter.getView(marker, convertView, markerViewContainer); - break; - } - } - } + public ViewGroup getMarkerViewContainer() { + return markerViewContainer; + } - if (view != null) { - if (marker.getWidth() == 0) { - if (view.getMeasuredWidth() == 0) { - //Ensure the marker's view is measured first - view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - } - marker.setWidth(view.getMeasuredWidth()); - marker.setHeight(view.getMeasuredHeight()); - } + public void addOnMarkerViewAddedListener(MarkerView markerView, OnMarkerViewAddedListener onMarkerViewAddedListener) { + markerViewAddedListenerMap.put(markerView.getId(), onMarkerViewAddedListener); + } - // update position on map - if (marker.getOffsetX() == MapboxConstants.UNMEASURED) { - int x = (int) (marker.getAnchorU() * marker.getWidth()); - int y = (int) (marker.getAnchorV() * marker.getHeight()); - marker.setOffset(x, y); - } + /** + * Default MarkerViewAdapter used for base class of {@link MarkerView} to adapt a MarkerView to + * an ImageView. + */ + public static class ImageMarkerViewAdapter extends MapboxMap.MarkerViewAdapter<MarkerView> { - // InfoWindow offset - int infoWindowOffsetX = (int) ((view.getMeasuredWidth() * marker.getInfoWindowAnchorU()) - marker.getOffsetX()); - int infoWindowOffsetY = (int) ((view.getMeasuredHeight() * marker.getInfoWindowAnchorV()) - marker.getOffsetY()); - marker.setTopOffsetPixels(infoWindowOffsetY); - marker.setRightOffsetPixels(infoWindowOffsetX); - } - } + private LayoutInflater inflater; - public ViewGroup getMarkerViewContainer() { - return markerViewContainer; + public ImageMarkerViewAdapter(Context context) { + super(context); + inflater = LayoutInflater.from(context); } - public void addOnMarkerViewAddedListener(MarkerView markerView, OnMarkerViewAddedListener onMarkerViewAddedListener) { - markerViewAddedListenerMap.put(markerView.getId(), onMarkerViewAddedListener); + @Nullable + @Override + public View getView(@NonNull MarkerView marker, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder; + if (convertView == null) { + viewHolder = new ViewHolder(); + convertView = inflater.inflate(R.layout.mapbox_view_image_marker, parent, false); + viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + viewHolder.imageView.setImageBitmap(marker.getIcon().getBitmap()); + return convertView; } - /** - * Default MarkerViewAdapter used for base class of {@link MarkerView} to adapt a MarkerView to - * an ImageView. - */ - public static class ImageMarkerViewAdapter extends MapboxMap.MarkerViewAdapter<MarkerView> { - - private LayoutInflater inflater; - - public ImageMarkerViewAdapter(Context context) { - super(context); - inflater = LayoutInflater.from(context); - } - - @Nullable - @Override - public View getView(@NonNull MarkerView marker, @Nullable View convertView, @NonNull ViewGroup parent) { - ViewHolder viewHolder; - if (convertView == null) { - viewHolder = new ViewHolder(); - convertView = inflater.inflate(R.layout.mapbox_view_image_marker, parent, false); - viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image); - convertView.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - viewHolder.imageView.setImageBitmap(marker.getIcon().getBitmap()); - return convertView; - } - - private static class ViewHolder { - ImageView imageView; - } + private static class ViewHolder { + ImageView imageView; } + } + + /** + * Interface definition invoked when the View of a MarkerView has been added to the map. + * <p> + * {@link MapboxMap#addMarker(BaseMarkerOptions)} + * and only when the related MarkerView is found in the viewport of the map. + * </p> + */ + public interface OnMarkerViewAddedListener { /** - * Interface definition invoked when the View of a MarkerView has been added to the map. - * <p> - * {@link MapboxMap#addMarker(BaseMarkerOptions)} - * and only when the related MarkerView is found in the viewport of the map. - * </p> + * Invoked when the View of a MarkerView has been added to the Map. + * + * @param markerView The MarkerView the View was added for */ - public interface OnMarkerViewAddedListener { - - /** - * Invoked when the View of a MarkerView has been added to the Map. - * - * @param markerView The MarkerView the View was added for - */ - void onViewAdded(@NonNull MarkerView markerView); - } + void onViewAdded(@NonNull MarkerView markerView); + } } |