diff options
author | Tobrun <tobrun.van.nuland@gmail.com> | 2017-11-10 06:16:35 +0100 |
---|---|---|
committer | Tobrun <tobrun.van.nuland@gmail.com> | 2017-11-10 06:16:35 +0100 |
commit | 225fba8a0aad5ea52ff878443f58d65bb2221d74 (patch) | |
tree | 8fef9e5353ec33f2fc0cec378a77d7146f9c67d0 /platform/android/MapboxGLAndroidSDK/src/main/java/com | |
parent | 6b5afaab7611cc59c86f749fd9130edb512b4945 (diff) | |
parent | 91dabd01dfcd52dc40aa1c1d1db9d3f48f7abc97 (diff) | |
download | qtlocation-mapboxgl-225fba8a0aad5ea52ff878443f58d65bb2221d74.tar.gz |
Merge branch 'release-agua' into tvn-merge-release
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com')
21 files changed, 770 insertions, 184 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java index 8a75176ccd..a024f0ab70 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java @@ -1,5 +1,7 @@ package com.mapbox.mapboxsdk; +import timber.log.Timber; + /** * Centralises the knowledge about "mapbox-gl" library loading. */ @@ -9,7 +11,10 @@ public class LibraryLoader { * Loads "libmapbox-gl.so" native shared library. */ public static void load() { - System.loadLibrary("mapbox-gl"); + try { + System.loadLibrary("mapbox-gl"); + } catch (UnsatisfiedLinkError error) { + Timber.e(error, "Failed to load native shared library."); + } } - } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java index 97a9ea94ee..fc448ccf7b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java @@ -80,12 +80,12 @@ public class MapboxConstants { /** * The currently used minimun scale factor to clamp to when a quick zoom gesture occurs */ - public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.65f; + public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.00f; /** * The currently used maximum scale factor to clamp to when a quick zoom gesture occurs */ - public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 1.35f; + public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.45f; /** * Fragment Argument Key for MapboxMapOptions diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java index e2626a026b..32aa250997 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java @@ -85,7 +85,14 @@ class HTTPRequest implements Callback { } mRequest = builder.build(); mCall = mClient.newCall(mRequest); - mCall.enqueue(this); + + // TODO remove code block for workaround in #10303 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { + mCall.enqueue(this); + } else { + // Calling execute instead of enqueue is a workaround for #10303 + onResponse(mCall, mCall.execute()); + } } catch (Exception exception) { onFailure(exception); } 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 c09c926eb5..9f256c341b 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 @@ -21,7 +21,6 @@ import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; -import com.mapbox.services.commons.geojson.Feature; import java.util.ArrayList; import java.util.List; @@ -41,7 +40,6 @@ import timber.log.Timber; */ class AnnotationManager { - private static final String LAYER_ID_SHAPE_ANNOTATIONS = "com.mapbox.annotations.shape."; private static final long NO_ANNOTATION_ID = -1; private final MapView mapView; @@ -50,7 +48,6 @@ class AnnotationManager { private final MarkerViewManager markerViewManager; private final LongSparseArray<Annotation> annotationsArray; private final List<Marker> selectedMarkers = new ArrayList<>(); - private final List<String> shapeAnnotationIds = new ArrayList<>(); private MapboxMap mapboxMap; private MapboxMap.OnMarkerClickListener onMarkerClickListener; @@ -58,13 +55,14 @@ class AnnotationManager { private MapboxMap.OnPolylineClickListener onPolylineClickListener; private Annotations annotations; + private ShapeAnnotations shapeAnnotations; private Markers markers; private Polygons polygons; private Polylines polylines; AnnotationManager(NativeMapView view, MapView mapView, LongSparseArray<Annotation> annotationsArray, MarkerViewManager markerViewManager, IconManager iconManager, Annotations annotations, - Markers markers, Polygons polygons, Polylines polylines) { + Markers markers, Polygons polygons, Polylines polylines, ShapeAnnotations shapeAnnotations) { this.mapView = mapView; this.annotationsArray = annotationsArray; this.markerViewManager = markerViewManager; @@ -73,6 +71,7 @@ class AnnotationManager { this.markers = markers; this.polygons = polygons; this.polylines = polylines; + this.shapeAnnotations = shapeAnnotations; if (view != null) { // null checking needed for unit tests view.addOnMapChangedListener(markerViewManager); @@ -122,9 +121,6 @@ class AnnotationManager { // do icon cleanup iconManager.iconCleanup(marker.getIcon()); } - } else { - // instanceOf Polygon/Polyline - shapeAnnotationIds.remove(annotation.getId()); } annotations.removeBy(annotation); } @@ -143,9 +139,6 @@ class AnnotationManager { } else { iconManager.iconCleanup(marker.getIcon()); } - } else { - // instanceOf Polygon/Polyline - shapeAnnotationIds.remove(annotation.getId()); } } annotations.removeBy(annotationList); @@ -167,9 +160,6 @@ class AnnotationManager { } else { iconManager.iconCleanup(marker.getIcon()); } - } else { - // instanceOf Polygon/Polyline - shapeAnnotationIds.remove(annotation.getId()); } } annotations.removeAll(); @@ -227,17 +217,11 @@ class AnnotationManager { // Polygon addPolygon(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap) { - Polygon polygon = polygons.addBy(polygonOptions, mapboxMap); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polygon.getId()); - return polygon; + return polygons.addBy(polygonOptions, mapboxMap); } List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap) { - List<Polygon> polygonList = polygons.addBy(polygonOptionsList, mapboxMap); - for (Polygon polygon : polygonList) { - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polygon.getId()); - } - return polygonList; + return polygons.addBy(polygonOptionsList, mapboxMap); } void updatePolygon(Polygon polygon) { @@ -257,17 +241,11 @@ class AnnotationManager { // Polyline addPolyline(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap) { - Polyline polyline = polylines.addBy(polylineOptions, mapboxMap); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polyline.getId()); - return polyline; + return polylines.addBy(polylineOptions, mapboxMap); } List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap) { - List<Polyline> polylineList = polylines.addBy(polylineOptionsList, mapboxMap); - for (Polyline polyline : polylineList) { - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polyline.getId()); - } - return polylineList; + return polylines.addBy(polylineOptionsList, mapboxMap); } void updatePolyline(Polyline polyline) { @@ -397,11 +375,11 @@ class AnnotationManager { // boolean onTap(PointF tapPoint) { - if (!shapeAnnotationIds.isEmpty()) { - ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); - long shapeAnnotationId = new ShapeAnnotationHitResolver(mapboxMap).execute(shapeAnnotationHit); - if (shapeAnnotationId != NO_ANNOTATION_ID) { - handleClickForShapeAnnotation(shapeAnnotationId); + ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); + Annotation annotation = new ShapeAnnotationHitResolver(shapeAnnotations).execute(shapeAnnotationHit); + if (annotation != null) { + if (handleClickForShapeAnnotation(annotation)) { + return true; } } @@ -418,16 +396,18 @@ class AnnotationManager { tapPoint.x + touchTargetSide, tapPoint.y + touchTargetSide ); - return new ShapeAnnotationHit(tapRect, shapeAnnotationIds.toArray(new String[shapeAnnotationIds.size()])); + return new ShapeAnnotationHit(tapRect); } - private void handleClickForShapeAnnotation(long shapeAnnotationId) { - Annotation annotation = getAnnotation(shapeAnnotationId); + private boolean handleClickForShapeAnnotation(Annotation annotation) { if (annotation instanceof Polygon && onPolygonClickListener != null) { onPolygonClickListener.onPolygonClick((Polygon) annotation); + return true; } else if (annotation instanceof Polyline && onPolylineClickListener != null) { onPolylineClickListener.onPolylineClick((Polyline) annotation); + return true; } + return false; } private MarkerHit getMarkerHitFromTouchArea(PointF tapPoint) { @@ -470,28 +450,19 @@ class AnnotationManager { private static class ShapeAnnotationHitResolver { - private MapboxMap mapboxMap; - - ShapeAnnotationHitResolver(MapboxMap mapboxMap) { - this.mapboxMap = mapboxMap; - } + private ShapeAnnotations shapeAnnotations; - public long execute(ShapeAnnotationHit shapeHit) { - long foundAnnotationId = NO_ANNOTATION_ID; - List<Feature> features = mapboxMap.queryRenderedFeatures(shapeHit.tapPoint, shapeHit.layerIds); - if (!features.isEmpty()) { - foundAnnotationId = getIdFromFeature(features.get(0)); - } - return foundAnnotationId; + ShapeAnnotationHitResolver(ShapeAnnotations shapeAnnotations) { + this.shapeAnnotations = shapeAnnotations; } - private long getIdFromFeature(Feature feature) { - try { - return Long.valueOf(feature.getId()); - } catch (NumberFormatException exception) { - Timber.e(exception, "Couldn't parse feature id to a long, with id: %s", feature.getId()); - return NO_ANNOTATION_ID; + public Annotation execute(ShapeAnnotationHit shapeHit) { + Annotation foundAnnotation = null; + List<Annotation> annotations = shapeAnnotations.obtainAllIn(shapeHit.tapPoint); + if (annotations.size() > 0) { + foundAnnotation = annotations.get(0); } + return foundAnnotation; } } @@ -567,11 +538,9 @@ class AnnotationManager { private static class ShapeAnnotationHit { private final RectF tapPoint; - private final String[] layerIds; - ShapeAnnotationHit(RectF tapRect, String[] layerIds) { - this.tapPoint = tapRect; - this.layerIds = layerIds; + ShapeAnnotationHit(RectF tapPoint) { + this.tapPoint = tapPoint; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java index 5113a0cc73..9ccff387f5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java @@ -1,12 +1,12 @@ package com.mapbox.mapboxsdk.maps; +import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -56,7 +56,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. private void showAttributionDialog() { attributionKeys = attributionMap.keySet().toArray(new String[attributionMap.size()]); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionsDialogTitle); builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionKeys), this); builder.show(); @@ -77,7 +77,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. } private void showTelemetryDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionTelemetryTitle); builder.setMessage(R.string.mapbox_attributionTelemetryMessage); builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java index 6f7d7c0080..cf780dcc3f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java @@ -1,5 +1,10 @@ package com.mapbox.mapboxsdk.maps; +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveCanceledListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; @@ -10,23 +15,32 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M private boolean idle = true; + private final List<OnCameraMoveStartedListener> onCameraMoveStartedListenerList = new ArrayList<>(); + private final List<OnCameraMoveCanceledListener> onCameraMoveCanceledListenerList = new ArrayList<>(); + private final List<OnCameraMoveListener> onCameraMoveListenerList = new ArrayList<>(); + private final List<OnCameraIdleListener> onCameraIdleListenerList = new ArrayList<>(); + private OnCameraMoveStartedListener onCameraMoveStartedListener; private OnCameraMoveCanceledListener onCameraMoveCanceledListener; private OnCameraMoveListener onCameraMoveListener; private OnCameraIdleListener onCameraIdleListener; + @Deprecated void setOnCameraMoveStartedListener(OnCameraMoveStartedListener onCameraMoveStartedListener) { this.onCameraMoveStartedListener = onCameraMoveStartedListener; } + @Deprecated void setOnCameraMoveCanceledListener(OnCameraMoveCanceledListener onCameraMoveCanceledListener) { this.onCameraMoveCanceledListener = onCameraMoveCanceledListener; } + @Deprecated void setOnCameraMoveListener(OnCameraMoveListener onCameraMoveListener) { this.onCameraMoveListener = onCameraMoveListener; } + @Deprecated void setOnCameraIdleListener(OnCameraIdleListener onCameraIdleListener) { this.onCameraIdleListener = onCameraIdleListener; } @@ -36,34 +50,106 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (!idle) { return; } - idle = false; + + // deprecated API if (onCameraMoveStartedListener != null) { onCameraMoveStartedListener.onCameraMoveStarted(reason); } + + // new API + if (!onCameraMoveStartedListenerList.isEmpty()) { + for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStartedListenerList) { + cameraMoveStartedListener.onCameraMoveStarted(reason); + } + } } @Override public void onCameraMove() { + // deprecated API if (onCameraMoveListener != null && !idle) { onCameraMoveListener.onCameraMove(); } + + // new API + if (!onCameraMoveListenerList.isEmpty() && !idle) { + for (OnCameraMoveListener cameraMoveListener : onCameraMoveListenerList) { + cameraMoveListener.onCameraMove(); + } + } } @Override public void onCameraMoveCanceled() { + // deprecated API if (onCameraMoveCanceledListener != null && !idle) { onCameraMoveCanceledListener.onCameraMoveCanceled(); } + + // new API + if (!onCameraMoveCanceledListenerList.isEmpty() && !idle) { + for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceledListenerList) { + cameraMoveCanceledListener.onCameraMoveCanceled(); + } + } } @Override public void onCameraIdle() { if (!idle) { idle = true; + // deprecated API if (onCameraIdleListener != null) { onCameraIdleListener.onCameraIdle(); } + + // new API + if (!onCameraIdleListenerList.isEmpty()) { + for (OnCameraIdleListener cameraIdleListener : onCameraIdleListenerList) { + cameraIdleListener.onCameraIdle(); + } + } + } + } + + void addOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { + onCameraIdleListenerList.add(listener); + } + + void removeOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { + if (onCameraIdleListenerList.contains(listener)) { + onCameraIdleListenerList.remove(listener); + } + } + + void addOnCameraMoveCancelListener(OnCameraMoveCanceledListener listener) { + onCameraMoveCanceledListenerList.add(listener); + } + + void removeOnCameraMoveCancelListener(OnCameraMoveCanceledListener listener) { + if (onCameraMoveCanceledListenerList.contains(listener)) { + onCameraMoveCanceledListenerList.remove(listener); + } + } + + void addOnCameraMoveStartedListener(OnCameraMoveStartedListener listener) { + onCameraMoveStartedListenerList.add(listener); + } + + void removeOnCameraMoveStartedListener(OnCameraMoveStartedListener listener) { + if (onCameraMoveStartedListenerList.contains(listener)) { + onCameraMoveStartedListenerList.remove(listener); + } + } + + void addOnCameraMoveListener(OnCameraMoveListener listener) { + onCameraMoveListenerList.add(listener); + } + + void removeOnCameraMoveListener(OnCameraMoveListener listener) { + if (onCameraMoveListenerList.contains(listener)) { + onCameraMoveListenerList.remove(listener); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java index b1d6df2103..80ffa973e7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java @@ -143,11 +143,14 @@ class IconManager { } void iconCleanup(Icon icon) { - int refCounter = iconMap.get(icon) - 1; - if (refCounter == 0) { - remove(icon); - } else { - updateIconRefCounter(icon, refCounter); + Integer refCounter = iconMap.get(icon); + if (refCounter != null) { + refCounter--; + if (refCounter == 0) { + remove(icon); + } else { + updateIconRefCounter(icon, refCounter); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java new file mode 100644 index 0000000000..b2f6cef3b0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java @@ -0,0 +1,17 @@ +package com.mapbox.mapboxsdk.maps; + +class Image { + private final byte[] buffer; + private final float pixelRatio; + private final String name; + private final int width; + private final int height; + + public Image(byte[] buffer, float pixelRatio, String name, int width, int height) { + this.buffer = buffer; + this.pixelRatio = pixelRatio; + this.name = name; + this.width = width; + this.height = height; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index 2394e52193..4120e164a4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -1,14 +1,19 @@ package com.mapbox.mapboxsdk.maps; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.PointF; import android.location.Location; import android.support.annotation.Nullable; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; +import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ScaleGestureDetector; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; @@ -57,8 +62,14 @@ final class MapGestureDetector { private boolean scaleGestureOccurred; private boolean recentScaleGestureOccurred; + private boolean scaleAnimating; private long scaleBeginTime; + private VelocityTracker velocityTracker; + private boolean wasZoomingIn; + private boolean wasClockwiseRotating; + private boolean rotateGestureOccurred; + MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings, TrackingSettings trackingSettings, AnnotationManager annotationManager, CameraChangeDispatcher cameraChangeDispatcher) { @@ -153,6 +164,12 @@ final class MapGestureDetector { // Handle two finger tap switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } else { + velocityTracker.clear(); + } + velocityTracker.addMovement(event); // First pointer down, reset scaleGestureOccurred, used to avoid triggering a fling after a scale gesture #7666 recentScaleGestureOccurred = false; transform.setGestureInProgress(true); @@ -203,11 +220,23 @@ final class MapGestureDetector { twoTap = false; transform.setGestureInProgress(false); + if (velocityTracker != null) { + velocityTracker.recycle(); + } + velocityTracker = null; break; case MotionEvent.ACTION_CANCEL: twoTap = false; transform.setGestureInProgress(false); + if (velocityTracker != null) { + velocityTracker.recycle(); + } + velocityTracker = null; + break; + case MotionEvent.ACTION_MOVE: + velocityTracker.addMovement(event); + velocityTracker.computeCurrentVelocity(1000); break; } @@ -430,7 +459,11 @@ final class MapGestureDetector { */ private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + private static final int ANIMATION_TIME_MULTIPLIER = 77; + private static final double ZOOM_DISTANCE_DIVIDER = 5; + private float scaleFactor = 1.0f; + private PointF scalePointBegin; // Called when two fingers first touch the screen @Override @@ -440,6 +473,7 @@ final class MapGestureDetector { } recentScaleGestureOccurred = true; + scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY()); scaleBeginTime = detector.getEventTime(); MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent( getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), @@ -447,15 +481,6 @@ final class MapGestureDetector { return true; } - // Called when fingers leave screen - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - scaleGestureOccurred = false; - scaleBeginTime = 0; - scaleFactor = 1.0f; - cameraChangeDispatcher.onCameraIdle(); - } - // Called each time a finger moves // Called for pinch zooms and quickzooms/quickscales @Override @@ -464,6 +489,7 @@ final class MapGestureDetector { return super.onScale(detector); } + wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0; if (tiltGestureOccurred) { return false; } @@ -478,7 +504,7 @@ final class MapGestureDetector { // If scale is large enough ignore a tap scaleFactor *= detector.getScaleFactor(); - if ((scaleFactor > 1.05f) || (scaleFactor < 0.95f)) { + if ((scaleFactor > 1.1f) || (scaleFactor < 0.9f)) { // notify camera change listener cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); scaleGestureOccurred = true; @@ -497,7 +523,6 @@ final class MapGestureDetector { // make an assumption here; if the zoom center is specified by the gesture, it's NOT going // to be in the center of the map. Therefore the zoom will translate the map center, so tracking // should be disabled. - trackingSettings.resetTrackingModesIfRequired(!quickZoom, false, false); // Scale the map if (focalPoint != null) { @@ -506,19 +531,79 @@ final class MapGestureDetector { } else if (quickZoom) { cameraChangeDispatcher.onCameraMove(); // clamp scale factors we feed to core #7514 - float scaleFactor = MathUtils.clamp(detector.getScaleFactor(), + float scaleFactor = detector.getScaleFactor(); + // around center map + double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2); + boolean negative = zoomBy < 0; + zoomBy = MathUtils.clamp(Math.abs(zoomBy), MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP, MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP); - // around center map - transform.zoomBy(Math.log(scaleFactor) / Math.log(Math.PI / 2), - uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + transform.zoomBy(negative ? -zoomBy : zoomBy, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + recentScaleGestureOccurred = true; } else { // around gesture transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), - detector.getFocusX(), detector.getFocusY()); + scalePointBegin.x, scalePointBegin.y); } return true; } + + // Called when fingers leave screen + @Override + public void onScaleEnd(final ScaleGestureDetector detector) { + if (rotateGestureOccurred || quickZoom) { + reset(); + return; + } + + double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity()); + if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) { + scaleAnimating = true; + double zoomAddition = calculateScale(velocityXY); + double currentZoom = transform.getRawZoom(); + long animationTime = (long) (Math.log(velocityXY) * ANIMATION_TIME_MULTIPLIER); + createScaleAnimator(currentZoom, zoomAddition, animationTime).start(); + } else if (!scaleAnimating) { + reset(); + } + } + + private void reset() { + scaleAnimating = false; + scaleGestureOccurred = false; + scaleBeginTime = 0; + scaleFactor = 1.0f; + cameraChangeDispatcher.onCameraIdle(); + } + + private double calculateScale(double velocityXY) { + double zoomAddition = (float) (Math.log(velocityXY) / ZOOM_DISTANCE_DIVIDER); + if (!wasZoomingIn) { + zoomAddition = -zoomAddition; + } + return zoomAddition; + } + + private Animator createScaleAnimator(double currentZoom, double zoomAddition, long animationTime) { + ValueAnimator animator = ValueAnimator.ofFloat((float) currentZoom, (float) (currentZoom + zoomAddition)); + animator.setDuration(animationTime); + animator.setInterpolator(new FastOutSlowInInterpolator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + reset(); + } + }); + return animator; + } } /** @@ -526,11 +611,13 @@ final class MapGestureDetector { */ private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { - private static final long ROTATE_INVOKE_WAIT_TIME = 1500; + private static final float ROTATE_INVOKE_ANGLE = 15.30f; + private static final float ROTATE_LIMITATION_ANGLE = 3.35f; + private static final float ROTATE_LIMITATION_DURATION = ROTATE_LIMITATION_ANGLE * 1.85f; private long beginTime = 0; - private float totalAngle = 0.0f; private boolean started = false; + private boolean animating = false; // Called when two fingers first touch the screen @Override @@ -546,15 +633,6 @@ final class MapGestureDetector { return true; } - // Called when the fingers leave the screen - @Override - public void onRotateEnd(RotateGestureDetector detector) { - // notify camera change listener - beginTime = 0; - totalAngle = 0.0f; - started = false; - } - // Called each time one of the two fingers moves // Called for rotation @Override @@ -563,18 +641,10 @@ final class MapGestureDetector { return false; } - // Ignore short touches in case it is a tap - // Also ignore small rotate - long time = detector.getEventTime(); - long interval = time - beginTime; - if (!started && (interval <= ViewConfiguration.getTapTimeout() || isScaleGestureActive(time))) { - return false; - } - // If rotate is large enough ignore a tap // Also is zoom already started, don't rotate - totalAngle += detector.getRotationDegreesDelta(); - if (totalAngle > 35.0f || totalAngle < -35.0f) { + float angle = detector.getRotationDegreesDelta(); + if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) { MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent( getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), MapboxEvent.GESTURE_ROTATION_START, transform)); @@ -585,13 +655,17 @@ final class MapGestureDetector { return false; } + wasClockwiseRotating = detector.getRotationDegreesDelta() > 0; + if (scaleBeginTime != 0) { + rotateGestureOccurred = true; + } + // rotation constitutes translation of anything except the center of // rotation, so cancel both location and bearing tracking if required trackingSettings.resetTrackingModesIfRequired(true, true, false); - // Get rotate value - double bearing = transform.getRawBearing(); - bearing += detector.getRotationDegreesDelta(); + // Calculate map bearing value + double bearing = transform.getRawBearing() + angle; // Rotate the map if (focalPoint != null) { @@ -604,11 +678,81 @@ final class MapGestureDetector { return true; } - private boolean isScaleGestureActive(long time) { - long scaleExecutionTime = time - scaleBeginTime; - boolean scaleGestureStarted = scaleBeginTime != 0; - boolean scaleOffsetTimeValid = scaleExecutionTime > ROTATE_INVOKE_WAIT_TIME; - return (scaleGestureStarted && scaleOffsetTimeValid) || scaleGestureOccurred; + // Called when the fingers leave the screen + @Override + public void onRotateEnd(RotateGestureDetector detector) { + long interval = detector.getEventTime() - beginTime; + if ((!started && (interval <= ViewConfiguration.getTapTimeout())) || scaleAnimating || interval > 500) { + reset(); + return; + } + + double angularVelocity = calculateVelocityVector(detector); + if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !animating) { + animateRotateVelocity(); + } else if (!animating) { + reset(); + } + } + + private void reset() { + beginTime = 0; + started = false; + animating = false; + rotateGestureOccurred = false; + } + + private void animateRotateVelocity() { + animating = true; + double currentRotation = transform.getRawBearing(); + double rotateAdditionDegrees = calculateVelocityInDegrees(); + createAnimator(currentRotation, rotateAdditionDegrees).start(); + } + + private double calculateVelocityVector(RotateGestureDetector detector) { + return ((detector.getFocusX() * velocityTracker.getYVelocity()) + + (detector.getFocusY() * velocityTracker.getXVelocity())) + / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2)); + } + + private double calculateVelocityInDegrees() { + double angleRadians = Math.atan2(velocityTracker.getXVelocity(), velocityTracker.getYVelocity()); + double angle = angleRadians / (Math.PI / 180); + if (angle <= 0) { + angle += 360; + } + + // limit the angle + angle = angle / ROTATE_LIMITATION_ANGLE; + + // correct direction + if (!wasClockwiseRotating) { + angle = -angle; + } + + return angle; + } + + private Animator createAnimator(double currentRotation, double rotateAdditionDegrees) { + ValueAnimator animator = ValueAnimator.ofFloat( + (float) currentRotation, + (float) (currentRotation + rotateAdditionDegrees) + ); + animator.setDuration((long) (Math.abs(rotateAdditionDegrees) * ROTATE_LIMITATION_DURATION)); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transform.setBearing((Float) animation.getAnimatedValue()); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + reset(); + } + }); + return animator; } } 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 92153f7f0b..c025a119b7 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 @@ -28,13 +28,14 @@ import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; -import com.mapbox.mapboxsdk.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer; import com.mapbox.mapboxsdk.maps.renderer.MapRenderer; +import com.mapbox.mapboxsdk.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer; import com.mapbox.mapboxsdk.maps.renderer.textureview.TextureViewMapRenderer; import com.mapbox.mapboxsdk.maps.widgets.CompassView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; +import com.mapbox.mapboxsdk.storage.FileSource; import com.mapbox.services.android.telemetry.MapboxTelemetry; import java.lang.annotation.Retention; @@ -175,8 +176,9 @@ public class MapView extends FrameLayout { Markers markers = new MarkerContainer(nativeMapView, this, annotationsArray, iconManager, markerViewManager); Polygons polygons = new PolygonContainer(nativeMapView, annotationsArray); Polylines polylines = new PolylineContainer(nativeMapView, annotationsArray); + ShapeAnnotations shapeAnnotations = new ShapeAnnotationContainer(nativeMapView, annotationsArray); AnnotationManager annotationManager = new AnnotationManager(nativeMapView, this, annotationsArray, - markerViewManager, iconManager, annotations, markers, polygons, polylines); + markerViewManager, iconManager, annotations, markers, polygons, polylines, shapeAnnotations); Transform transform = new Transform(nativeMapView, annotationManager.getMarkerViewManager(), trackingSettings, cameraChangeDispatcher); @@ -191,8 +193,10 @@ public class MapView extends FrameLayout { annotationManager, cameraChangeDispatcher); mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); + // overlain zoom buttons mapZoomButtonController = new MapZoomButtonController(new ZoomButtonsController(this)); - MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform); + MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform, + cameraChangeDispatcher, getWidth(), getHeight()); mapZoomButtonController.bind(uiSettings, zoomListener); compassView.injectCompassAnimationListener(createCompassAnimationListener(cameraChangeDispatcher)); @@ -345,8 +349,10 @@ public class MapView extends FrameLayout { */ @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); - mapboxMap.onSaveInstanceState(outState); + if (mapboxMap != null) { + outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); + mapboxMap.onSaveInstanceState(outState); + } } /** @@ -355,6 +361,7 @@ public class MapView extends FrameLayout { @UiThread public void onStart() { ConnectivityReceiver.instance(getContext()).activate(); + FileSource.getInstance(getContext()).activate(); if (mapboxMap != null) { mapboxMap.onStart(); } @@ -385,8 +392,12 @@ public class MapView extends FrameLayout { */ @UiThread public void onStop() { - mapboxMap.onStop(); + if (mapboxMap != null) { + // map was destroyed before it was started + mapboxMap.onStop(); + } ConnectivityReceiver.instance(getContext()).deactivate(); + FileSource.getInstance(getContext()).deactivate(); } /** @@ -406,6 +417,10 @@ public class MapView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (!isMapInitialized() || !isZoomButtonControllerInitialized()) { + return super.onTouchEvent(event); + } + if (event.getAction() == MotionEvent.ACTION_DOWN) { mapZoomButtonController.setVisible(true); } @@ -434,11 +449,18 @@ public class MapView extends FrameLayout { @Override public boolean onGenericMotionEvent(MotionEvent event) { + if (mapGestureDetector == null) { + return super.onGenericMotionEvent(event); + } return mapGestureDetector.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } @Override public boolean onHoverEvent(MotionEvent event) { + if (!isZoomButtonControllerInitialized()) { + return super.onHoverEvent(event); + } + switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_MOVE: @@ -495,7 +517,7 @@ public class MapView extends FrameLayout { if (destroyed) { return; } - if (nativeMapView == null) { + if (!isMapInitialized()) { mapboxMapOptions.styleUrl(url); return; } @@ -512,7 +534,7 @@ public class MapView extends FrameLayout { return; } - if (!isInEditMode() && nativeMapView != null) { + if (!isInEditMode() && isMapInitialized()) { nativeMapView.resizeView(width, height); } } @@ -526,7 +548,9 @@ public class MapView extends FrameLayout { @CallSuper protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mapZoomButtonController.setVisible(false); + if (isZoomButtonControllerInitialized()) { + mapZoomButtonController.setVisible(false); + } } // Called when view is hidden and shown @@ -536,7 +560,7 @@ public class MapView extends FrameLayout { return; } - if (mapZoomButtonController != null) { + if (isZoomButtonControllerInitialized()) { mapZoomButtonController.setVisible(visibility == View.VISIBLE); } } @@ -598,6 +622,14 @@ public class MapView extends FrameLayout { } } + private boolean isMapInitialized() { + return nativeMapView != null; + } + + private boolean isZoomButtonControllerInitialized() { + return mapZoomButtonController != null; + } + MapboxMap getMapboxMap() { return mapboxMap; } @@ -883,16 +915,23 @@ public class MapView extends FrameLayout { } } - private class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener { + private static class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener { private final MapGestureDetector mapGestureDetector; private final UiSettings uiSettings; private final Transform transform; + private final CameraChangeDispatcher cameraChangeDispatcher; + private final float mapWidth; + private final float mapHeight; - MapZoomControllerListener(MapGestureDetector detector, UiSettings uiSettings, Transform transform) { + MapZoomControllerListener(MapGestureDetector detector, UiSettings uiSettings, Transform transform, + CameraChangeDispatcher dispatcher, float mapWidth, float mapHeight) { this.mapGestureDetector = detector; this.uiSettings = uiSettings; this.transform = transform; + this.cameraChangeDispatcher = dispatcher; + this.mapWidth = mapWidth; + this.mapHeight = mapHeight; } // Not used @@ -905,6 +944,7 @@ public class MapView extends FrameLayout { @Override public void onZoom(boolean zoomIn) { if (uiSettings.isZoomGesturesEnabled()) { + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_ANIMATION); onZoom(zoomIn, mapGestureDetector.getFocalPoint()); } } @@ -913,7 +953,7 @@ public class MapView extends FrameLayout { if (focalPoint != null) { transform.zoom(zoomIn, focalPoint); } else { - PointF centerPoint = new PointF(getMeasuredWidth() / 2, getMeasuredHeight() / 2); + PointF centerPoint = new PointF(mapWidth / 2, mapHeight / 2); transform.zoom(zoomIn, centerPoint); } } 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 83b4dc4e84..6bf8342efb 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 @@ -47,6 +47,7 @@ import com.mapbox.services.commons.geojson.Feature; import com.mapbox.services.commons.geojson.Geometry; import java.lang.reflect.ParameterizedType; +import java.util.HashMap; import java.util.List; import timber.log.Timber; @@ -463,6 +464,13 @@ public final class MapboxMap { } /** + * Adds an images to be used in the map's style + */ + public void addImages(@NonNull HashMap<String, Bitmap> images) { + nativeMapView.addImages(images); + } + + /** * Removes an image from the map's style * * @param name the name of the image to remove @@ -709,6 +717,10 @@ public final class MapboxMap { // MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo` // invalidate camera position to provide OnCameraChange event. invalidateCameraPosition(); + + if (callback != null) { + callback.onFinish(); + } } }); } @@ -1753,39 +1765,123 @@ public final class MapboxMap { * Sets a callback that is invoked when camera movement has ended. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraIdleListener(OnCameraIdleListener)} + * and {@link #removeOnCameraIdleListener(OnCameraIdleListener)} instead */ + @Deprecated public void setOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { cameraChangeDispatcher.setOnCameraIdleListener(listener); } /** + * Adds a callback that is invoked when camera movement has ended. + * + * @param listener the listener to notify + */ + public void addOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { + cameraChangeDispatcher.addOnCameraIdleListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement has ended. + * + * @param listener the listener to remove + */ + public void removeOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { + cameraChangeDispatcher.removeOnCameraIdleListener(listener); + } + + /** * Sets a callback that is invoked when camera movement was cancelled. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveCancelListener(OnCameraMoveCanceledListener)} and + * {@link #removeOnCameraMoveCancelListener(OnCameraMoveCanceledListener)} instead */ + @Deprecated public void setOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { cameraChangeDispatcher.setOnCameraMoveCanceledListener(listener); } /** + * Adds a callback that is invoked when camera movement was cancelled. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { + cameraChangeDispatcher.addOnCameraMoveCancelListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement was cancelled. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { + cameraChangeDispatcher.removeOnCameraMoveCancelListener(listener); + } + + /** * Sets a callback that is invoked when camera movement has started. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveStartedListener(OnCameraMoveStartedListener)} and + * {@link #removeOnCameraMoveStartedListener(OnCameraMoveStartedListener)} instead */ + @Deprecated public void setOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { cameraChangeDispatcher.setOnCameraMoveStartedListener(listener); } /** + * Adds a callback that is invoked when camera movement has started. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { + cameraChangeDispatcher.addOnCameraMoveStartedListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement has started. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { + cameraChangeDispatcher.removeOnCameraMoveStartedListener(listener); + } + + /** * Sets a callback that is invoked when camera position changes. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveListener(OnCameraMoveListener)} and + * {@link #removeOnCameraMoveListener(OnCameraMoveListener)}instead */ + @Deprecated public void setOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { cameraChangeDispatcher.setOnCameraMoveListener(listener); } /** + * Adds a callback that is invoked when camera position changes. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { + cameraChangeDispatcher.addOnCameraMoveListener(listener); + } + + /** + * Removes a callback that is invoked when camera position changes. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { + cameraChangeDispatcher.removeOnCameraMoveListener(listener); + } + + /** * Sets a callback that's invoked on every frame rendered to the map view. * * @param listener The callback that's invoked on every frame rendered to the map view. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java index 072382ce07..2c2f07a112 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java @@ -98,15 +98,8 @@ class MarkerContainer implements Markers { @NonNull @Override public List<Marker> obtainAllIn(@NonNull RectF rectangle) { - // convert Rectangle to be density depedent - float pixelRatio = nativeMapView.getPixelRatio(); - RectF rect = new RectF(rectangle.left / pixelRatio, - rectangle.top / pixelRatio, - rectangle.right / pixelRatio, - rectangle.bottom / pixelRatio); - + RectF rect = nativeMapView.getDensityDependantRectangle(rectangle); long[] ids = nativeMapView.queryPointAnnotations(rect); - List<Long> idsList = new ArrayList<>(ids.length); for (long id : ids) { idsList.add(id); 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 bd8a54783e..8b6bce69e2 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 @@ -4,6 +4,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; +import android.os.AsyncTask; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -34,7 +35,9 @@ import com.mapbox.services.commons.geojson.Geometry; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import timber.log.Timber; @@ -468,6 +471,13 @@ final class NativeMapView { return nativeQueryPointAnnotations(rect); } + public long[] queryShapeAnnotations(RectF rectF) { + if (isDestroyedOn("queryShapeAnnotations")) { + return new long[] {}; + } + return nativeQueryShapeAnnotations(rectF); + } + public void addAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels) { if (isDestroyedOn("addAnnotationIcon")) { return; @@ -746,6 +756,7 @@ final class NativeMapView { if (isDestroyedOn("addImage")) { return; } + // Check/correct config if (image.getConfig() != Bitmap.Config.ARGB_8888) { image = image.copy(Bitmap.Config.ARGB_8888, false); @@ -762,6 +773,14 @@ final class NativeMapView { nativeAddImage(name, image.getWidth(), image.getHeight(), pixelRatio, buffer.array()); } + public void addImages(@NonNull HashMap<String, Bitmap> bitmapHashMap) { + if (isDestroyedOn("addImages")) { + return; + } + //noinspection unchecked + new BitmapImageConversionTask(this).execute(bitmapHashMap); + } + public void removeImage(String name) { if (isDestroyedOn("removeImage")) { return; @@ -825,6 +844,15 @@ final class NativeMapView { return pixelRatio; } + RectF getDensityDependantRectangle(final RectF rectangle) { + return new RectF( + rectangle.left / pixelRatio, + rectangle.top / pixelRatio, + rectangle.right / pixelRatio, + rectangle.bottom / pixelRatio + ); + } + // // Callbacks // @@ -927,6 +955,8 @@ final class NativeMapView { private native long[] nativeQueryPointAnnotations(RectF rect); + private native long[] nativeQueryShapeAnnotations(RectF rect); + private native void nativeAddAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels); private native void nativeRemoveAnnotationIcon(String symbol); @@ -1006,6 +1036,8 @@ final class NativeMapView { private native void nativeAddImage(String name, int width, int height, float pixelRatio, byte[] array); + private native void nativeAddImages(Image[] images); + private native void nativeRemoveImage(String name); private native Bitmap nativeGetImage(String name); @@ -1093,4 +1125,55 @@ final class NativeMapView { }); } + + + // + // Image conversion + // + + private static class BitmapImageConversionTask extends AsyncTask<HashMap<String, Bitmap>, Void, List<Image>> { + + private NativeMapView nativeMapView; + + BitmapImageConversionTask(NativeMapView nativeMapView) { + this.nativeMapView = nativeMapView; + } + + @Override + protected List<Image> doInBackground(HashMap<String, Bitmap>... params) { + HashMap<String, Bitmap> bitmapHashMap = params[0]; + + List<Image> images = new ArrayList<>(); + ByteBuffer buffer; + String name; + Bitmap bitmap; + + for (Map.Entry<String, Bitmap> stringBitmapEntry : bitmapHashMap.entrySet()) { + name = stringBitmapEntry.getKey(); + bitmap = stringBitmapEntry.getValue(); + + if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); + } + + buffer = ByteBuffer.allocate(bitmap.getByteCount()); + bitmap.copyPixelsToBuffer(buffer); + + float density = bitmap.getDensity() == Bitmap.DENSITY_NONE ? Bitmap.DENSITY_NONE : bitmap.getDensity(); + float pixelRatio = density / DisplayMetrics.DENSITY_DEFAULT; + + images.add(new Image(buffer.array(), pixelRatio, name, bitmap.getWidth(), bitmap.getHeight())); + } + + return images; + } + + @Override + protected void onPostExecute(List<Image> images) { + super.onPostExecute(images); + if (nativeMapView != null && !nativeMapView.isDestroyedOn("nativeAddImages")) { + nativeMapView.nativeAddImages(images.toArray(new Image[images.size()])); + } + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotationContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotationContainer.java new file mode 100644 index 0000000000..6ded2f32fb --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotationContainer.java @@ -0,0 +1,38 @@ +package com.mapbox.mapboxsdk.maps; + +import android.graphics.RectF; +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; + +import java.util.ArrayList; +import java.util.List; + +class ShapeAnnotationContainer implements ShapeAnnotations { + + private final NativeMapView nativeMapView; + private final LongSparseArray<Annotation> annotations; + + ShapeAnnotationContainer(NativeMapView nativeMapView, LongSparseArray<Annotation> annotations) { + this.nativeMapView = nativeMapView; + this.annotations = annotations; + } + + @Override + public List<Annotation> obtainAllIn(RectF rectangle) { + RectF rect = nativeMapView.getDensityDependantRectangle(rectangle); + long[] annotationIds = nativeMapView.queryShapeAnnotations(rect); + return getAnnotationsFromIds(annotationIds); + } + + private List<Annotation> getAnnotationsFromIds(long[] annotationIds) { + List<Annotation> shapeAnnotations = new ArrayList<>(); + for (long annotationId : annotationIds) { + Annotation annotation = annotations.get(annotationId); + if (annotation != null) { + shapeAnnotations.add(annotation); + } + } + return shapeAnnotations; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotations.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotations.java new file mode 100644 index 0000000000..f9b2ca10db --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotations.java @@ -0,0 +1,13 @@ +package com.mapbox.mapboxsdk.maps; + +import android.graphics.RectF; + +import com.mapbox.mapboxsdk.annotations.Annotation; + +import java.util.List; + +interface ShapeAnnotations { + + List<Annotation> obtainAllIn(RectF rectF); + +} 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 index 6f63c2eba8..0366e50627 100644 --- 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 @@ -98,9 +98,6 @@ final class Transform implements MapView.OnMapChangedListener { cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(OnCameraMoveStartedListener.REASON_API_ANIMATION); mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); - if (callback != null) { - callback.onFinish(); - } cameraChangeDispatcher.onCameraIdle(); } } @@ -210,6 +207,10 @@ final class Transform implements MapView.OnMapChangedListener { return cameraPosition.zoom; } + double getRawZoom() { + return mapView.getZoom(); + } + void zoom(boolean zoomIn, @NonNull PointF focalPoint) { CameraPosition cameraPosition = invalidateCameraPosition(); if (cameraPosition != null) { @@ -221,6 +222,17 @@ final class Transform implements MapView.OnMapChangedListener { } } + void zoom(double zoomAddition, @NonNull PointF focalPoint, long duration) { + CameraPosition cameraPosition = invalidateCameraPosition(); + if (cameraPosition != null) { + int newZoom = (int) Math.round(cameraPosition.zoom + zoomAddition); + setZoom(newZoom, focalPoint, duration); + } else { + // we are not transforming, notify about being idle + cameraChangeDispatcher.onCameraIdle(); + } + } + void setZoom(double zoom, @NonNull PointF focalPoint) { setZoom(zoom, focalPoint, 0); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index 45f72af1c5..1e604c9bef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -6,10 +6,10 @@ import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; -import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -22,7 +22,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap; * use {@link com.mapbox.mapboxsdk.maps.UiSettings}. * </p> */ -public final class CompassView extends AppCompatImageView implements Runnable { +public final class CompassView extends ImageView implements Runnable { public static final long TIME_WAIT_IDLE = 500; public static final long TIME_MAP_NORTH_ANIMATION = 150; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index f210729037..1b9a156352 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -300,10 +300,19 @@ public class OfflineRegion { /** * Pause or resume downloading of regional resources. + * <p> + * After a download has been completed, you are required to reset the state of the region to STATE_INACTIVE. + * </p> * * @param state the download state */ public void setDownloadState(@DownloadState int state) { + if (state == STATE_ACTIVE) { + fileSource.activate(); + } else { + fileSource.deactivate(); + } + this.state = state; setOfflineRegionDownloadState(state); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java index 5206606c83..5deedc3e63 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -4,6 +4,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Matrix; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -262,35 +263,59 @@ public class MapSnapshotter { nativeCancel(); } - protected void addOverlay(Bitmap original) { + /** + * Draw an overlay on the map snapshot. + * + * @param mapSnapshot the map snapshot to draw the overlay on + */ + protected void addOverlay(MapSnapshot mapSnapshot) { + Bitmap original = mapSnapshot.getBitmap(); + Canvas canvas = new Canvas(original); + addLogo(canvas, original); + } + + /** + * Draw a logo on the canvas created from the map snapshot. + * + * @param canvas the canvas to draw the bitmap on + * @param original the map snapshot image + */ + private void addLogo(Canvas canvas, Bitmap original) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); float margin = displayMetrics.density * LOGO_MARGIN_DP; - Canvas canvas = new Canvas(original); + Bitmap logo = createScaledLogo(original); + canvas.drawBitmap(logo, margin, original.getHeight() - logo.getHeight() - margin, null); + } + + /** + * Create a scaled logo for a map snapshot. + * + * @param snapshot the map snapshot where the logo should be placed on + * @return the scaled bitmap logo + */ + private Bitmap createScaledLogo(Bitmap snapshot) { + Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null); + float scale = calculateLogoScale(snapshot, logo); + Matrix matrix = new Matrix(); + matrix.postScale(scale, scale); + return Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true); + } - // Decode just the boundaries - BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); - bitmapOptions.inJustDecodeBounds = true; - BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, bitmapOptions); - int srcWidth = bitmapOptions.outWidth; - int srcHeight = bitmapOptions.outHeight; - - // Ratio, preferred dimensions and resulting sample size - float widthRatio = displayMetrics.widthPixels / original.getWidth(); - float heightRatio = displayMetrics.heightPixels / original.getHeight(); - float prefWidth = srcWidth / widthRatio; - float prefHeight = srcHeight / heightRatio; - int sampleSize = MapSnaphotUtil.calculateInSampleSize(bitmapOptions, (int) prefWidth, (int) prefHeight); - - // Scale bitmap - bitmapOptions.inJustDecodeBounds = false; - bitmapOptions.inScaled = true; - bitmapOptions.inSampleSize = sampleSize; - bitmapOptions.inDensity = srcWidth; - bitmapOptions.inTargetDensity = (int) prefWidth * bitmapOptions.inSampleSize; - Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, bitmapOptions); - - float scaledHeight = bitmapOptions.outHeight * heightRatio / bitmapOptions.inSampleSize; - canvas.drawBitmap(logo, margin, original.getHeight() - scaledHeight - margin, null); + /** + * Calculates the scale of the logo, only allow downscaling. + * + * @param snapshot the bitmap of the map snapshot + * @param logo the bitmap of the mapbox logo + * @return the scale value + */ + private float calculateLogoScale(Bitmap snapshot, Bitmap logo) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + float widthRatio = displayMetrics.widthPixels / snapshot.getWidth(); + float heightRatio = displayMetrics.heightPixels / snapshot.getHeight(); + float prefWidth = logo.getWidth() / widthRatio; + float prefHeight = logo.getHeight() / heightRatio; + float calculatedScale = Math.min(prefWidth / logo.getWidth(), prefHeight / logo.getHeight()) * 2; + return calculatedScale < 1 ? calculatedScale : 1.0f; } /** @@ -302,7 +327,7 @@ public class MapSnapshotter { protected void onSnapshotReady(MapSnapshot snapshot) { if (callback != null) { if (snapshot.isShowLogo()) { - addOverlay(snapshot.getBitmap()); + addOverlay(snapshot); } callback.onSnapshotReady(snapshot); reset(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index a968cdf192..41dc449b50 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -72,7 +72,7 @@ public class FileSource { MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL, MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL); } catch (PackageManager.NameNotFoundException exception) { - Timber.e(exception,"Failed to read the package metadata: "); + Timber.e(exception, "Failed to read the package metadata: "); } catch (Exception exception) { Timber.e(exception, "Failed to read the storage key: "); } @@ -119,17 +119,39 @@ public class FileSource { } private long nativePtr; + private long activeCounter; + private boolean wasPaused; private FileSource(String cachePath, AssetManager assetManager) { initialize(Mapbox.getAccessToken(), cachePath, assetManager); } + public void activate() { + activeCounter++; + if (activeCounter == 1 && wasPaused) { + wasPaused = false; + resume(); + } + } + + public void deactivate() { + activeCounter--; + if (activeCounter == 0) { + wasPaused = true; + pause(); + } + } + public native void setAccessToken(@NonNull String accessToken); public native String getAccessToken(); public native void setApiBaseUrl(String baseUrl); + private native void resume(); + + private native void pause(); + /** * Sets a callback for transforming URLs requested from the internet * <p> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java index 14b18b00dc..1c0e439afc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java @@ -5,6 +5,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.graphics.drawable.DrawableCompat; @@ -30,10 +31,15 @@ public class ColorUtils { */ @ColorInt public static int getPrimaryColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorPrimary, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorPrimary", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_blue); + } } /** @@ -44,10 +50,15 @@ public class ColorUtils { */ @ColorInt public static int getPrimaryDarkColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorPrimaryDark", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_blue); + } } /** @@ -58,10 +69,15 @@ public class ColorUtils { */ @ColorInt public static int getAccentColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorAccent, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorAccent", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_gray); + } } /** @@ -122,4 +138,12 @@ public class ColorUtils { throw new ConversionException("Not a valid rgb/rgba value"); } } + + private static int getColorCompat(Context context, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getResources().getColor(id, context.getTheme()); + } else { + return context.getResources().getColor(id); + } + } } |