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 | 1030 |
1 files changed, 560 insertions, 470 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 cd333268a1..409b0bf195 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 @@ -6,6 +6,7 @@ import android.graphics.RectF; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.util.LongSparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -29,527 +30,616 @@ import java.util.Map; * This class is responsible for managing a {@link MarkerView} item. * </p> */ -public class MarkerViewManager { - - private Map<MarkerView, View> markerViewMap; - private MapboxMap mapboxMap; - private MapView mapView; - private List<MapboxMap.MarkerViewAdapter> markerViewAdapters; - private long viewMarkerBoundsUpdateTime; - private MapboxMap.OnMarkerViewClickListener onMarkerViewClickListener; - private ImageMarkerViewAdapter defaultMarkerViewAdapter; - - /** - * Creates an instance of MarkerViewManager. - * - * @param mapboxMap the MapboxMap associated with the MarkerViewManager - * @param mapView the MapView associated with the MarkerViewManager - */ - public MarkerViewManager(@NonNull MapboxMap mapboxMap, @NonNull MapView mapView) { - this.mapboxMap = mapboxMap; - this.markerViewAdapters = new ArrayList<>(); - this.mapView = mapView; - this.markerViewMap = new HashMap<>(); - this.defaultMarkerViewAdapter = new ImageMarkerViewAdapter(mapView.getContext()); - this.markerViewAdapters.add(defaultMarkerViewAdapter); +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 boolean enabled; + private long updateTime; + 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(); } - - /** - * 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); - } + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + 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); } - - /** - * Animate a MarkerView with a given rotation. - * - * @param marker the MarkerView to rotate by - * @param rotation the rotation by value - */ - public void animateRotationBy(@NonNull MarkerView marker, float rotation) { - View convertView = markerViewMap.get(marker); - if (convertView != null) { - AnimatorUtils.rotateBy(convertView, rotation); - } + } + + /** + * 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); } - - /** - * 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 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 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 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); } - - /** - * 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); - } + } + + /** + * 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); } - } - /** - * 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); - } - } + 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); + } + + /** + * 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()); } - - /** - * 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); - } - } - if (callbackToMap) { - mapboxMap.deselectMarker(marker); - } - marker.setSelected(false); + } + + /** + * 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); } + } } - - /** - * Animate a MarkerView to a selected state. - * - * @param marker the MarkerView object to select - */ - public void select(@NonNull MarkerView marker) { - select(marker, true); + if (callbackToMap) { + mapboxMap.deselectMarker(marker); } - - /** - * Animate a MarkerView to a selected state. - * - * @param marker the MarkerView object to select - * @param callbackToMap indicates if select marker must be called on 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); - } + 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 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.selectMarker(marker); } + } + marker.setSelected(true); + convertView.bringToFront(); } - - /** - * 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); + } + + /** + * 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. - * <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(); + 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); + } } + } } - - /** - * Get view representation from a MarkerView. - * <p> - * If marker is not found in current viewport, null is returned. - * </p> - * - * @param marker the marker to get the view for - * @return the android SDK View object - */ - @Nullable - public View getView(MarkerView marker) { - return markerViewMap.get(marker); + 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"); } - @Nullable - public MapboxMap.MarkerViewAdapter getViewAdapter(MarkerView markerView) { - MapboxMap.MarkerViewAdapter adapter = null; - for (MapboxMap.MarkerViewAdapter a : markerViewAdapters) { - if (a.getMarkerClass().equals(markerView.getClass())) { - adapter = a; - } + if (!markerViewAdapters.contains(markerViewAdapter)) { + markerViewAdapters.add(markerViewAdapter); + invalidateViewMarkersInVisibleRegion(); + } + } + + /** + * 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 (enabled) { + long currentTime = SystemClock.elapsedRealtime(); + if (currentTime < updateTime) { + return; + } + invalidateViewMarkersInVisibleRegion(); + updateTime = currentTime + 250; + } + } + + /** + * 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 use 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 any more and will add new - * ones for each found Marker in the changed viewport. - * </p> - */ - public void invalidateViewMarkersInVisibleRegion() { - RectF mapViewRect = new RectF(0, 0, mapView.getWidth(), mapView.getHeight()); - List<MarkerView> markers = mapView.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 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; } + } + } - // 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, mapView); - 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); - mapView.getMarkerViewContainer().addView(adaptedView); - } - } - } - } - } + 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); } - // trigger update to make newly added ViewMarker visible, - // these would only be updated when the map is moved. - update(); + 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); } + } - public void onClickMarkerView(MarkerView markerView) { - boolean clickHandled = false; + public ViewGroup getMarkerViewContainer() { + return markerViewContainer; + } - MapboxMap.MarkerViewAdapter adapter = getViewAdapter(markerView); - View view = getView(markerView); - if (adapter == null || view == null) { - // not a valid state - return; - } + public void addOnMarkerViewAddedListener(MarkerView markerView, OnMarkerViewAddedListener onMarkerViewAddedListener) { + markerViewAddedListenerMap.put(markerView.getId(), onMarkerViewAddedListener); + } - if (onMarkerViewClickListener != null) { - clickHandled = onMarkerViewClickListener.onMarkerClick(markerView, view, adapter); - } + /** + * Default MarkerViewAdapter used for base class of {@link MarkerView} to adapt a MarkerView to + * an ImageView. + */ + private static class ImageMarkerViewAdapter extends MapboxMap.MarkerViewAdapter<MarkerView> { - if (!clickHandled) { - ensureInfoWindowOffset(markerView); - select(markerView, view, adapter); - } - } - - //TODO: This whole method is a stopgap for: https://github.com/mapbox/mapbox-gl-native/issues/5384 - 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, mapView); - break; - } - } - } + private LayoutInflater inflater; - 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()); - } + ImageMarkerViewAdapter(Context context) { + super(context); + inflater = LayoutInflater.from(context); + } - // 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); - } + @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; + } - // 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 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 { /** - * Default MarkerViewAdapter used for base class of MarkerView to adapt a MarkerView to an ImageView + * Invoked when the View of a MarkerView has been added to the Map. + * + * @param markerView The MarkerView the View was added for */ - 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.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; - } - } + void onViewAdded(@NonNull MarkerView markerView); + } } |