diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps')
7 files changed, 387 insertions, 302 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index 4b26be558f..9e08d93bfc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -30,7 +30,7 @@ import java.util.List; * Exposes convenience methods to add/remove/update all subtypes of annotations found in com.mapbox.mapboxsdk.annotations. * </p> */ -class AnnotationManager { +class AnnotationManager implements MapView.OnMapChangedListener { private NativeMapView nativeMapView; private IconManager iconManager; @@ -41,6 +41,7 @@ class AnnotationManager { private List<Marker> selectedMarkers; private MapboxMap.OnMarkerClickListener onMarkerClickListener; + private boolean isWaitingForRenderInvoke; AnnotationManager(NativeMapView view, IconManager iconManager, InfoWindowManager manager) { this.nativeMapView = view; @@ -48,6 +49,19 @@ class AnnotationManager { this.infoWindowManager = manager; this.selectedMarkers = new ArrayList<>(); this.annotations = new LongSparseArray<>(); + + if (view != null) { + // null checking needed for unit tests + view.addOnMapChangedListener(this); + } + } + + @Override + public void onMapChanged(@MapView.MapChange int change) { + if (isWaitingForRenderInvoke && change == MapView.DID_FINISH_RENDERING_FRAME_FULLY_RENDERED) { + isWaitingForRenderInvoke = false; + markerViewManager.invalidateViewMarkersInVisibleRegion(); + } } // @@ -194,18 +208,24 @@ class AnnotationManager { } MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions, @NonNull MapboxMap mapboxMap) { + isWaitingForRenderInvoke = true; MarkerView marker = prepareViewMarker(markerOptions); marker.setMapboxMap(mapboxMap); long id = nativeMapView.addMarker(marker); marker.setId(id); annotations.put(id, marker); - markerViewManager.invalidateViewMarkersInVisibleRegion(); return marker; } List<MarkerView> addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions, @NonNull MapboxMap mapboxMap) { List<MarkerView> markers = new ArrayList<>(); for (BaseMarkerViewOptions markerViewOption : markerViewOptions) { + // if last marker + if (markerViewOptions.indexOf(markerViewOption) == markerViewOptions.size() - 1) { + // get notified when render occurs to invalidate and draw MarkerViews + isWaitingForRenderInvoke = true; + } + // add marker to map MarkerView marker = prepareViewMarker(markerViewOption); marker.setMapboxMap(mapboxMap); long id = nativeMapView.addMarker(marker); @@ -283,7 +303,7 @@ class AnnotationManager { } if (infoWindowManager.isInfoWindowValidForMarker(marker) || infoWindowManager.getInfoWindowAdapter() != null) { - infoWindowManager.getInfoWindows().add(marker.showInfoWindow(mapboxMap, mapboxMap.getMapView())); + infoWindowManager.add(marker.showInfoWindow(mapboxMap, mapboxMap.getMapView())); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java index 7b58807973..791f310b4c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java @@ -80,4 +80,8 @@ class InfoWindowManager { MapboxMap.OnInfoWindowCloseListener getOnInfoWindowCloseListener() { return onInfoWindowCloseListener; } + + public void add(InfoWindow infoWindow) { + infoWindows.add(infoWindow); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index a9f2ed23fc..5840e574c8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -87,7 +87,6 @@ import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import static com.mapbox.mapboxsdk.utils.MathUtils.convertNativeBearing; @@ -126,7 +125,6 @@ public class MapView extends FrameLayout { private Projection projection; - private CopyOnWriteArrayList<OnMapChangedListener> onMapChangedListener; private ZoomButtonsController zoomButtonsController; private ConnectivityReceiver connectivityReceiver; private float screenDensity = 1.0f; @@ -153,7 +151,6 @@ public class MapView extends FrameLayout { private boolean styleWasSet = false; private List<OnMapReadyCallback> onMapReadyCallbackList; - private MapboxMap.CancelableCallback cameraCancelableCallback; private SnapshotRequest snapshotRequest; private boolean onStartCalled; @@ -192,7 +189,6 @@ public class MapView extends FrameLayout { initialLoad = true; onMapReadyCallbackList = new ArrayList<>(); - onMapChangedListener = new CopyOnWriteArrayList<>(); View view = LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_internal, this); setWillNotDraw(false); @@ -483,7 +479,7 @@ public class MapView extends FrameLayout { } // invalidate camera to update overlain views with correct tilt value - invalidateCameraPosition(); + mapboxMap.invalidateCameraPosition(); } else if (change == REGION_IS_CHANGING || change == REGION_DID_CHANGE || change == DID_FINISH_LOADING_MAP) { mapboxMap.getMarkerViewManager().scheduleViewMarkerInvalidation(); @@ -705,11 +701,11 @@ public class MapView extends FrameLayout { // Center coordinate // - LatLng getCenterCoordinate(){ + LatLng getCenterCoordinate() { return nativeMapView.getLatLng(); } - void setCenterCoordinate(LatLng centerCoordinate){ + void setCenterCoordinate(LatLng centerCoordinate) { nativeMapView.setLatLng(centerCoordinate); } @@ -737,7 +733,7 @@ public class MapView extends FrameLayout { return; } long duration = animated ? MapboxConstants.ANIMATION_DURATION : 0; - cancelTransitions(); + mapboxMap.cancelTransitions(); // Out of range directions are normalised in setBearing nativeMapView.setBearing(-direction, duration); } @@ -747,7 +743,7 @@ public class MapView extends FrameLayout { return; } myLocationView.setBearing(0); - cancelTransitions(); + mapboxMap.cancelTransitions(); nativeMapView.resetNorth(); } @@ -817,16 +813,13 @@ public class MapView extends FrameLayout { private void zoom(boolean zoomIn, float x, float y) { // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); if (zoomIn) { nativeMapView.scaleBy(2.0, x / screenDensity, y / screenDensity, MapboxConstants.ANIMATION_DURATION); } else { nativeMapView.scaleBy(0.5, x / screenDensity, y / screenDensity, MapboxConstants.ANIMATION_DURATION); } - - // work around to invalidate camera position - postDelayed(new ZoomInvalidator(mapboxMap), MapboxConstants.ANIMATION_DURATION); } // @@ -1068,76 +1061,6 @@ public class MapView extends FrameLayout { } // - // Mapbox Core GL Camera - // - - private void cancelTransitions(){ - if (cameraCancelableCallback != null) { - cameraCancelableCallback.onCancel(); - cameraCancelableCallback = null; - } - nativeMapView.cancelTransitions(); - } - - void jumpTo(double bearing, LatLng center, double pitch, double zoom) { - if (destroyed) { - return; - } - cancelTransitions(); - nativeMapView.jumpTo(bearing, center, pitch, zoom); - } - - void easeTo(double bearing, LatLng center, long duration, double pitch, double zoom, boolean easingInterpolator, @Nullable final MapboxMap.CancelableCallback cancelableCallback) { - if (destroyed) { - return; - } - cancelTransitions(); - - // Register callbacks early enough - if (cancelableCallback != null) { - cameraCancelableCallback = cancelableCallback; - addOnMapChangedListener(new OnMapChangedListener() { - @Override - public void onMapChanged(@MapChange int change) { - if (change == REGION_DID_CHANGE_ANIMATED && cameraCancelableCallback != null) { - cameraCancelableCallback.onFinish(); - cameraCancelableCallback = null; - // Clean up after self - removeOnMapChangedListener(this); - } - } - }); - } - - nativeMapView.easeTo(bearing, center, duration, pitch, zoom, easingInterpolator); - } - - void flyTo(double bearing, LatLng center, long duration, double pitch, double zoom, @Nullable final MapboxMap.CancelableCallback cancelableCallback) { - if (destroyed) { - return; - } - cancelTransitions(); - - // Register callbacks early enough - if (cancelableCallback != null) { - cameraCancelableCallback = cancelableCallback; - addOnMapChangedListener(new OnMapChangedListener() { - @Override - public void onMapChanged(@MapChange int change) { - if (change == REGION_DID_CHANGE_ANIMATED && cameraCancelableCallback != null) { - cancelableCallback.onFinish(); - cameraCancelableCallback = null; - // Clean up after self - removeOnMapChangedListener(this); - } - } - }); - } - - nativeMapView.flyTo(bearing, center, duration, pitch, zoom); - } - - // // Rendering // @@ -1268,14 +1191,9 @@ public class MapView extends FrameLayout { } } - CameraPosition invalidateCameraPosition() { - if (destroyed) { - return new CameraPosition.Builder().build(); - } - CameraPosition position = new CameraPosition.Builder(nativeMapView.getCameraValues()).build(); + void updateCameraPosition(@NonNull CameraPosition position) { myLocationView.setCameraPosition(position); mapboxMap.getMarkerViewManager().setTilt((float) position.tilt); - return position; } double getBearing() { @@ -1560,7 +1478,7 @@ public class MapView extends FrameLayout { return false; } // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); return true; } @@ -1649,12 +1567,12 @@ public class MapView extends FrameLayout { return false; } - resetTrackingModesIfRequired(true, false); + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(true, false); double decelerationRate = 1; // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); double offsetX = velocityX * decelerationRate / 4 / screenDensity; double offsetY = velocityY * decelerationRate / 4 / screenDensity; @@ -1689,9 +1607,9 @@ public class MapView extends FrameLayout { requestDisallowInterceptTouchEvent(true); // reset tracking if needed - resetTrackingModesIfRequired(true, false); + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(true, false); // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); // Scroll the map nativeMapView.moveBy(-distanceX / screenDensity, -distanceY / screenDensity); @@ -1762,7 +1680,7 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); // Gesture is a quickzoom if there aren't two fingers quickZoom = !twoTap; @@ -1771,7 +1689,7 @@ public class MapView extends FrameLayout { // to be in the center of the map. Therefore the zoom will translate the map center, so tracking // should be disabled. - resetTrackingModesIfRequired(!quickZoom, false); + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(!quickZoom, false); // Scale the map if (focalPoint != null) { // arround user provided focal point @@ -1843,12 +1761,12 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); // rotation constitutes translation of anything except the center of // rotation, so cancel both location and bearing tracking if required - resetTrackingModesIfRequired(true, true); + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(true, true); // Get rotate value double bearing = nativeMapView.getBearing(); @@ -1919,7 +1837,7 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); // Get tilt value (scale and clamp) double pitch = getTilt(); @@ -1986,7 +1904,7 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); // Move left nativeMapView.moveBy(scrollDist / screenDensity, 0.0 / screenDensity); @@ -1998,8 +1916,8 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); - + mapboxMap.cancelTransitions(); + // Move right nativeMapView.moveBy(-scrollDist / screenDensity, 0.0 / screenDensity); return true; @@ -2010,8 +1928,8 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); - + mapboxMap.cancelTransitions(); + // Move up nativeMapView.moveBy(0.0 / screenDensity, scrollDist / screenDensity); return true; @@ -2022,7 +1940,7 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); + mapboxMap.cancelTransitions(); // Move down nativeMapView.moveBy(0.0 / screenDensity, -scrollDist / screenDensity); @@ -2102,8 +2020,8 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); - + mapboxMap.cancelTransitions(); + // Scroll the map nativeMapView.moveBy(-10.0 * event.getX() / screenDensity, -10.0 * event.getY() / screenDensity); return true; @@ -2198,8 +2116,8 @@ public class MapView extends FrameLayout { } // Cancel any animation - cancelTransitions(); - + mapboxMap.cancelTransitions(); + // Get the vertical scroll amount, one click = 1 float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL); @@ -2289,7 +2207,7 @@ public class MapView extends FrameLayout { */ public void addOnMapChangedListener(@Nullable OnMapChangedListener listener) { if (listener != null) { - onMapChangedListener.add(listener); + nativeMapView.addOnMapChangedListener(listener); } } @@ -2301,7 +2219,7 @@ public class MapView extends FrameLayout { */ public void removeOnMapChangedListener(@Nullable OnMapChangedListener listener) { if (listener != null) { - onMapChangedListener.remove(listener); + nativeMapView.removeOnMapChangedListener(listener); } } @@ -2309,14 +2227,7 @@ public class MapView extends FrameLayout { // Called via JNI from NativeMapView // Forward to any listeners protected void onMapChanged(int mapChange) { - if (onMapChangedListener != null) { - OnMapChangedListener listener; - final Iterator<OnMapChangedListener> iterator = onMapChangedListener.iterator(); - while (iterator.hasNext()) { - listener = iterator.next(); - listener.onMapChanged(mapChange); - } - } + nativeMapView.onMapChangedEventDispatch(mapChange); } // @@ -2382,42 +2293,6 @@ public class MapView extends FrameLayout { ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; } - void resetTrackingModesIfRequired(boolean translate, boolean rotate) { - TrackingSettings trackingSettings = mapboxMap.getTrackingSettings(); - - // if tracking is on, and we should dismiss tracking with gestures, and this is a scroll action, turn tracking off - if (translate && !trackingSettings.isLocationTrackingDisabled() && trackingSettings.isDismissLocationTrackingOnGesture()) { - resetLocationTrackingMode(); - } - - // reset bearing tracking only on rotate - if (rotate && !trackingSettings.isBearingTrackingDisabled() && trackingSettings.isDismissBearingTrackingOnGesture()) { - resetBearingTrackingMode(); - } - } - - void resetTrackingModesIfRequired(CameraPosition cameraPosition) { - resetTrackingModesIfRequired(cameraPosition.target != null, cameraPosition.bearing != -1); - } - - private void resetLocationTrackingMode() { - try { - TrackingSettings trackingSettings = mapboxMap.getTrackingSettings(); - trackingSettings.setMyLocationTrackingMode(MyLocationTracking.TRACKING_NONE); - } catch (SecurityException ignore) { - // User did not accept location permissions - } - } - - private void resetBearingTrackingMode() { - try { - TrackingSettings trackingSettings = mapboxMap.getTrackingSettings(); - trackingSettings.setMyBearingTrackingMode(MyBearingTracking.NONE); - } catch (SecurityException ignore) { - // User did not accept location permissions - } - } - // // Compass // @@ -2653,21 +2528,6 @@ public class MapView extends FrameLayout { } } - private static class ZoomInvalidator implements Runnable { - - private MapboxMap mapboxMap; - - ZoomInvalidator(MapboxMap mapboxMap) { - this.mapboxMap = mapboxMap; - } - - @Override - public void run() { - // invalidate camera position - mapboxMap.getCameraPosition(); - } - } - /** * Definition of a map change event. * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index ab6476f6e6..8e42e794be 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -5,7 +5,6 @@ import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; import android.location.Location; -import android.os.SystemClock; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -45,7 +44,6 @@ import com.mapbox.services.commons.geojson.Feature; import java.lang.reflect.ParameterizedType; import java.util.List; -import java.util.concurrent.TimeUnit; /** * The general class to interact with in the Android Mapbox SDK. It exposes the entry point for all @@ -64,9 +62,7 @@ public class MapboxMap { private TrackingSettings trackingSettings; private MyLocationViewSettings myLocationViewSettings; private Projection projection; - private CameraPosition cameraPosition; - private boolean invalidCameraPosition; - + private Transform transform; private boolean myLocationEnabled; private MapboxMap.OnMapClickListener onMapClickListener; @@ -78,7 +74,6 @@ public class MapboxMap { private MapboxMap.OnMyLocationTrackingModeChangeListener onMyLocationTrackingModeChangeListener; private MapboxMap.OnMyBearingTrackingModeChangeListener onMyBearingTrackingModeChangeListener; private MapboxMap.OnFpsChangedListener onFpsChangedListener; - private MapboxMap.OnCameraChangeListener onCameraChangeListener; private AnnotationManager annotationManager; private InfoWindowManager infoWindowManager; @@ -88,12 +83,18 @@ public class MapboxMap { MapboxMap(@NonNull MapView mapView, IconManager iconManager) { this.mapView = mapView; - this.mapView.addOnMapChangedListener(new MapChangeCameraPositionListener()); uiSettings = new UiSettings(mapView); trackingSettings = new TrackingSettings(this.mapView, uiSettings); projection = new Projection(mapView); infoWindowManager = new InfoWindowManager(); annotationManager = new AnnotationManager(mapView.getNativeMapView(), iconManager, infoWindowManager); + + // TODO inject NativeMapView https://github.com/mapbox/mapbox-gl-native/issues/4100 + NativeMapView nativeMapView = mapView.getNativeMapView(); + if (nativeMapView != null) { + transform = new Transform(nativeMapView, this); + nativeMapView.addOnMapChangedListener(new CameraInvalidator()); + } } // Style @@ -379,6 +380,16 @@ public class MapboxMap { // /** + * Cancels ongoing animations. + * <p> + * This invokes the {@link CancelableCallback} for ongoing camera updates. + * </p> + */ + public void cancelTransitions() { + transform.cancelTransitions(); + } + + /** * Gets the current position of the camera. * The CameraPosition returned is a snapshot of the current position, and will not automatically update when the * camera moves. @@ -386,10 +397,7 @@ public class MapboxMap { * @return The current position of the Camera. */ public final CameraPosition getCameraPosition() { - if (invalidCameraPosition) { - invalidateCameraPosition(); - } - return cameraPosition; + return transform.getCameraPosition(); } /** @@ -400,7 +408,7 @@ public class MapboxMap { * @param cameraPosition the camera position to set */ public void setCameraPosition(@NonNull CameraPosition cameraPosition) { - moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); + moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), null); } /** @@ -424,17 +432,13 @@ public class MapboxMap { * @param callback the callback to be invoked when an animation finishes or is canceled */ @UiThread - public final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { - cameraPosition = update.getCameraPosition(this); - mapView.resetTrackingModesIfRequired(cameraPosition); - mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); - if (callback != null) { - callback.onFinish(); - } - - if (onCameraChangeListener != null) { - onCameraChangeListener.onCameraChange(this.cameraPosition); - } + public final void moveCamera(final CameraUpdate update, final MapboxMap.CancelableCallback callback) { + mapView.post(new Runnable() { + @Override + public void run() { + transform.moveCamera(update, callback); + } + }); } /** @@ -489,11 +493,44 @@ public class MapboxMap { easeCamera(update, durationMs, true, callback); } + /** + * Gradually move the camera by a specified duration in milliseconds, zoom will not be affected + * unless specified within {@link CameraUpdate}. A callback can be used to be notified when + * easing the camera stops. If {@link #getCameraPosition()} is called during the animation, it + * will return the current location of the camera in flight. + * <p> + * Note that this will cancel location tracking mode if enabled. + * </p> + * + * @param update The change that should be applied to the camera. + * @param durationMs The duration of the animation in milliseconds. This must be strictly + * positive, otherwise an IllegalArgumentException will be thrown. + * @param easingInterpolator True for easing interpolator, false for linear. + */ @UiThread public final void easeCamera(CameraUpdate update, int durationMs, boolean easingInterpolator) { easeCamera(update, durationMs, easingInterpolator, null); } + /** + * Gradually move the camera by a specified duration in milliseconds, zoom will not be affected + * unless specified within {@link CameraUpdate}. A callback can be used to be notified when + * easing the camera stops. If {@link #getCameraPosition()} is called during the animation, it + * will return the current location of the camera in flight. + * <p> + * Note that this will cancel location tracking mode if enabled. + * </p> + * + * @param update The change that should be applied to the camera. + * @param durationMs The duration of the animation in milliseconds. This must be strictly + * positive, otherwise an IllegalArgumentException will be thrown. + * @param easingInterpolator True for easing interpolator, false for linear. + * @param callback An optional callback to be notified from the main thread when the animation + * stops. If the animation stops due to its natural completion, the callback + * will be notified with onFinish(). If the animation stops due to interruption + * by a later camera movement or a user gesture, onCancel() will be called. + * Do not update or ease the camera from within onCancel(). + */ @UiThread public final void easeCamera( CameraUpdate update, int durationMs, boolean easingInterpolator, final MapboxMap.CancelableCallback callback) { @@ -501,33 +538,34 @@ public class MapboxMap { easeCamera(update, durationMs, easingInterpolator, true, callback); } - @UiThread - public final void easeCamera( - CameraUpdate update, int durationMs, boolean easingInterpolator, boolean resetTrackingMode, final MapboxMap.CancelableCallback callback) { - // dismiss tracking, moving camera is equal to a gesture - cameraPosition = update.getCameraPosition(this); - if (resetTrackingMode) { - mapView.resetTrackingModesIfRequired(cameraPosition); - } - - mapView.easeTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, - cameraPosition.zoom, easingInterpolator, new CancelableCallback() { - @Override - public void onCancel() { - if (callback != null) { - callback.onCancel(); - } - invalidateCameraPosition(); - } - - @Override - public void onFinish() { - if (callback != null) { - callback.onFinish(); - } - invalidateCameraPosition(); - } - }); + /** + * Gradually move the camera by a specified duration in milliseconds, zoom will not be affected + * unless specified within {@link CameraUpdate}. A callback can be used to be notified when + * easing the camera stops. If {@link #getCameraPosition()} is called during the animation, it + * will return the current location of the camera in flight. + * <p> + * Note that this will cancel location tracking mode if enabled. + * </p> + * + * @param update The change that should be applied to the camera. + * @param durationMs The duration of the animation in milliseconds. This must be strictly + * positive, otherwise an IllegalArgumentException will be thrown. + * @param resetTrackingMode True to reset tracking modes if required, false to ignore + * @param easingInterpolator True for easing interpolator, false for linear. + * @param callback An optional callback to be notified from the main thread when the animation + * stops. If the animation stops due to its natural completion, the callback + * will be notified with onFinish(). If the animation stops due to interruption + * by a later camera movement or a user gesture, onCancel() will be called. + * Do not update or ease the camera from within onCancel(). + */ + @UiThread + public final void easeCamera(final CameraUpdate update, final int durationMs, final boolean easingInterpolator, final boolean resetTrackingMode, final MapboxMap.CancelableCallback callback) { + mapView.post(new Runnable() { + @Override + public void run() { + transform.easeCamera(update, durationMs, easingInterpolator, resetTrackingMode, callback); + } + }); } /** @@ -596,58 +634,22 @@ public class MapboxMap { * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread - public final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { - cameraPosition = update.getCameraPosition(this); - mapView.resetTrackingModesIfRequired(cameraPosition); - mapView.flyTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, - cameraPosition.zoom, new CancelableCallback() { - @Override - public void onCancel() { - if (callback != null) { - callback.onCancel(); - } - invalidateCameraPosition(); - } - - @Override - public void onFinish() { - if (onCameraChangeListener != null) { - onCameraChangeListener.onCameraChange(cameraPosition); - } - - if (callback != null) { - callback.onFinish(); - } - invalidateCameraPosition(); - } - }); - } - - /** - * Converts milliseconds to nanoseconds - * - * @param durationMs The time in milliseconds - * @return time in nanoseconds - */ - private long getDurationNano(long durationMs) { - return durationMs > 0 ? TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS) : 0; + public final void animateCamera(final CameraUpdate update, final int durationMs, final MapboxMap.CancelableCallback callback) { + mapView.post(new Runnable() { + @Override + public void run() { + transform.animateCamera(update, durationMs, callback); + } + }); } /** * Invalidates the current camera position by reconstructing it from mbgl */ - private void invalidateCameraPosition() { - if (invalidCameraPosition) { - invalidCameraPosition = false; - - CameraPosition cameraPosition = mapView.invalidateCameraPosition(); - if (cameraPosition != null) { - this.cameraPosition = cameraPosition; - } - - if (onCameraChangeListener != null) { - onCameraChangeListener.onCameraChange(this.cameraPosition); - } + void invalidateCameraPosition() { + CameraPosition cameraPosition = transform.invalidateCameraPosition(); + if (cameraPosition != null) { + mapView.updateCameraPosition(cameraPosition); } } @@ -659,7 +661,7 @@ public class MapboxMap { * Resets the map view to face north. */ public void resetNorth() { - mapView.resetNorth(); + transform.resetNorth(); } // @@ -875,7 +877,8 @@ public class MapboxMap { @UiThread @NonNull - public List<MarkerView> addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions) { + public List<MarkerView> addMarkerViews(@NonNull List<? extends + BaseMarkerViewOptions> markerViewOptions) { return annotationManager.addMarkerViews(markerViewOptions, this); } @@ -897,7 +900,8 @@ public class MapboxMap { */ @UiThread @NonNull - public List<Marker> addMarkers(@NonNull List<? extends BaseMarkerOptions> markerOptionsList) { + public List<Marker> addMarkers(@NonNull List<? extends + BaseMarkerOptions> markerOptionsList) { return annotationManager.addMarkers(markerOptionsList, this); } @@ -1239,7 +1243,7 @@ public class MapboxMap { return infoWindowManager.isAllowConcurrentMultipleOpenInfoWindows(); } - // used by MapView + // Internal API List<InfoWindow> getInfoWindows() { return infoWindowManager.getInfoWindows(); } @@ -1248,6 +1252,11 @@ public class MapboxMap { return annotationManager; } + Transform getTransform() { + return transform; + } + + // // Padding // @@ -1299,7 +1308,7 @@ public class MapboxMap { */ @UiThread public void setOnCameraChangeListener(@Nullable OnCameraChangeListener listener) { - onCameraChangeListener = listener; + transform.setOnCameraChangeListener(listener); } /** @@ -1410,7 +1419,8 @@ public class MapboxMap { * use null. */ @UiThread - public void setOnInfoWindowLongClickListener(@Nullable OnInfoWindowLongClickListener listener) { + public void setOnInfoWindowLongClickListener(@Nullable OnInfoWindowLongClickListener + listener) { infoWindowManager.setOnInfoWindowLongClickListener(listener); } @@ -1493,7 +1503,8 @@ public class MapboxMap { * To unset the callback, use null. */ @UiThread - public void setOnMyLocationChangeListener(@Nullable MapboxMap.OnMyLocationChangeListener listener) { + public void setOnMyLocationChangeListener(@Nullable MapboxMap.OnMyLocationChangeListener + listener) { mapView.setOnMyLocationChangeListener(listener); } @@ -1504,12 +1515,14 @@ public class MapboxMap { * To unset the callback, use null. */ @UiThread - public void setOnMyLocationTrackingModeChangeListener(@Nullable MapboxMap.OnMyLocationTrackingModeChangeListener listener) { + public void setOnMyLocationTrackingModeChangeListener + (@Nullable MapboxMap.OnMyLocationTrackingModeChangeListener listener) { onMyLocationTrackingModeChangeListener = listener; } // used by MapView - MapboxMap.OnMyLocationTrackingModeChangeListener getOnMyLocationTrackingModeChangeListener() { + MapboxMap.OnMyLocationTrackingModeChangeListener getOnMyLocationTrackingModeChangeListener + () { return onMyLocationTrackingModeChangeListener; } @@ -1520,7 +1533,8 @@ public class MapboxMap { * To unset the callback, use null. */ @UiThread - public void setOnMyBearingTrackingModeChangeListener(@Nullable OnMyBearingTrackingModeChangeListener listener) { + public void setOnMyBearingTrackingModeChangeListener + (@Nullable OnMyBearingTrackingModeChangeListener listener) { onMyBearingTrackingModeChangeListener = listener; } @@ -1582,7 +1596,8 @@ public class MapboxMap { */ @UiThread @NonNull - public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... layerIds) { + public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... + layerIds) { return mapView.getNativeMapView().queryRenderedFeatures(coordinates, layerIds); } @@ -1595,11 +1610,26 @@ public class MapboxMap { */ @UiThread @NonNull - public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable String... layerIds) { + public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable String... + layerIds) { return mapView.getNativeMapView().queryRenderedFeatures(coordinates, layerIds); } // + // Innner classes + // + + private class CameraInvalidator implements MapView.OnMapChangedListener { + + @Override + public void onMapChanged(@MapView.MapChange int change) { + if (change == MapView.REGION_DID_CHANGE_ANIMATED) { + invalidateCameraPosition(); + } + } + } + + // // Interfaces // @@ -1963,23 +1993,4 @@ public class MapboxMap { */ void onSnapshotReady(Bitmap snapshot); } - - private class MapChangeCameraPositionListener implements MapView.OnMapChangedListener { - - private static final long UPDATE_RATE_MS = 400; - private long previousUpdateTimestamp = 0; - - @Override - public void onMapChanged(@MapView.MapChange int change) { - if (change >= MapView.REGION_WILL_CHANGE && change <= MapView.REGION_DID_CHANGE_ANIMATED) { - invalidCameraPosition = true; - long currentTime = SystemClock.elapsedRealtime(); - if (currentTime < previousUpdateTimestamp) { - return; - } - invalidateCameraPosition(); - previousUpdateTimestamp = currentTime + UPDATE_RATE_MS; - } - } - } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index e7e3e647af..cdd275ee34 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -30,6 +30,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; // Class that wraps the native methods for convenience final class NativeMapView { @@ -44,6 +45,8 @@ final class NativeMapView { private final float pixelRatio; + private CopyOnWriteArrayList<MapView.OnMapChangedListener> onMapChangedListeners; + // // Static methods // @@ -83,7 +86,7 @@ final class NativeMapView { if (totalMemory < 0) { throw new IllegalArgumentException("totalMemory cannot be negative."); } - + onMapChangedListeners = new CopyOnWriteArrayList<>(); this.mapView = mapView; nativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath, pixelRatio, availableProcessors, totalMemory); } @@ -778,4 +781,24 @@ final class NativeMapView { private native Feature[] nativeQueryRenderedFeaturesForBox(long nativeMapViewPtr, float left, float top, float right, float bottom, String[] layerIds); private native void nativeSetAPIBaseURL(long nativeMapViewPtr, String baseUrl); + + // + // MapChangeEvents + // + + void addOnMapChangedListener(@NonNull MapView.OnMapChangedListener listener) { + onMapChangedListeners.add(listener); + } + + void removeOnMapChangedListener(@NonNull MapView.OnMapChangedListener listener) { + onMapChangedListeners.remove(listener); + } + + void onMapChangedEventDispatch(int mapChange) { + if (onMapChangedListeners != null) { + for (MapView.OnMapChangedListener onMapChangedListener : onMapChangedListeners) { + onMapChangedListener.onMapChanged(mapChange); + } + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java index c32d4a8906..7f655fbeef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java @@ -3,6 +3,7 @@ package com.mapbox.mapboxsdk.maps; import android.support.annotation.NonNull; import android.support.annotation.UiThread; +import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; @@ -211,4 +212,27 @@ public class TrackingSettings { return uiSettings.isScrollGesturesEnabled() && (dismissLocationTrackingOnGesture || myLocationTrackingMode == MyLocationTracking.TRACKING_NONE); } + + /** + * Reset the tracking modes as necessary. Location tracking is reset if the map center is changed, + * bearing tracking if there is a rotation. + * + * @param translate + * @param rotate + */ + public void resetTrackingModesIfRequired(boolean translate, boolean rotate) { + // if tracking is on, and we should dismiss tracking with gestures, and this is a scroll action, turn tracking off + if (translate && !isLocationTrackingDisabled() && isDismissLocationTrackingOnGesture()) { + setMyLocationTrackingMode(MyLocationTracking.TRACKING_NONE); + } + + // reset bearing tracking only on rotate + if (rotate && !isBearingTrackingDisabled() && isDismissBearingTrackingOnGesture()) { + setMyBearingTrackingMode(MyBearingTracking.NONE); + } + } + + public void resetTrackingModesIfRequired(CameraPosition cameraPosition) { + resetTrackingModesIfRequired(cameraPosition.target != null, cameraPosition.bearing != -1); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java new file mode 100644 index 0000000000..1532bb6745 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java @@ -0,0 +1,143 @@ +package com.mapbox.mapboxsdk.maps; + +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdate; + +import java.util.concurrent.TimeUnit; + +import static com.mapbox.mapboxsdk.maps.MapView.REGION_DID_CHANGE_ANIMATED; + +/** + * Resembles the current Map transformation. + * <p> + * Responsible for synchronising {@link CameraPosition} state and notifying {@link com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraChangeListener}. + * </p> + */ +class Transform { + + private NativeMapView mapView; + private MapboxMap mapboxMap; + + private CameraPosition cameraPosition; + private MapboxMap.CancelableCallback cameraCancelableCallback; + private MapboxMap.OnCameraChangeListener onCameraChangeListener; + + Transform(NativeMapView mapView, MapboxMap mapboxMap) { + this.mapView = mapView; + this.mapboxMap = mapboxMap; + } + + // + // Camera API + // + + @UiThread + public final CameraPosition getCameraPosition() { + if (cameraPosition == null) { + cameraPosition = invalidateCameraPosition(); + } + return cameraPosition; + } + + @UiThread + final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { + cameraPosition = update.getCameraPosition(mapboxMap); + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(cameraPosition); + cancelTransitions(); + mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); + + // MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo` + // invalidate camera position to provide OnCameraChange event. + mapboxMap.invalidateCameraPosition(); + if (callback != null) { + callback.onFinish(); + } + } + + @UiThread + final void easeCamera(CameraUpdate update, int durationMs, boolean easingInterpolator, boolean resetTrackingMode, final MapboxMap.CancelableCallback callback) { + cameraPosition = update.getCameraPosition(mapboxMap); + if (resetTrackingMode) { + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(cameraPosition); + } + + cancelTransitions(); + if (callback != null) { + cameraCancelableCallback = callback; + mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() { + @Override + public void onMapChanged(@MapView.MapChange int change) { + if (change == REGION_DID_CHANGE_ANIMATED && cameraCancelableCallback != null) { + cameraCancelableCallback.onFinish(); + cameraCancelableCallback = null; + mapView.removeOnMapChangedListener(this); + } + } + }); + } + + mapView.easeTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, cameraPosition.zoom, easingInterpolator); + } + + @UiThread + final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { + cameraPosition = update.getCameraPosition(mapboxMap); + mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(cameraPosition); + + cancelTransitions(); + if (callback != null) { + cameraCancelableCallback = callback; + mapView.addOnMapChangedListener(new MapView.OnMapChangedListener() { + @Override + public void onMapChanged(@MapView.MapChange int change) { + if (change == REGION_DID_CHANGE_ANIMATED && cameraCancelableCallback != null) { + cameraCancelableCallback.onFinish(); + cameraCancelableCallback = null; + mapView.removeOnMapChangedListener(this); + } + } + }); + } + + mapView.flyTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, cameraPosition.zoom); + } + + @UiThread + @Nullable + CameraPosition invalidateCameraPosition() { + CameraPosition cameraPosition = null; + if (mapView != null) { + cameraPosition = new CameraPosition.Builder(mapView.getCameraValues()).build(); + this.cameraPosition = cameraPosition; + if (onCameraChangeListener != null) { + onCameraChangeListener.onCameraChange(this.cameraPosition); + } + } + return cameraPosition; + } + + void cancelTransitions() { + if (cameraCancelableCallback != null) { + cameraCancelableCallback.onCancel(); + cameraCancelableCallback = null; + } + mapView.cancelTransitions(); + } + + @UiThread + void resetNorth() { + cancelTransitions(); + mapView.resetNorth(); + } + + void setOnCameraChangeListener(@Nullable MapboxMap.OnCameraChangeListener listener) { + this.onCameraChangeListener = listener; + } + + private long getDurationNano(long durationMs) { + return durationMs > 0 ? TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS) : 0; + } +} |