diff options
author | Tobrun <tobrun.van.nuland@gmail.com> | 2016-12-06 13:46:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-06 13:46:49 +0100 |
commit | 9a9e7978db67276cfaab97e00f2b56eeb0222b12 (patch) | |
tree | 0da54e64117a64195ee46060e01d270a86c1e45d /platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk | |
parent | 890b681b182f7d538237604307da487f3619d1b1 (diff) | |
download | qtlocation-mapboxgl-9a9e7978db67276cfaab97e00f2b56eeb0222b12.tar.gz |
[android] - Refactor dependencies, introduce focused components (#7189)
* [android] - refactor dependencies
* ignore tests
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk')
19 files changed, 1899 insertions, 2036 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index 1073818ca4..ce2d3d1577 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -9,6 +9,7 @@ import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ImageView; import com.mapbox.mapboxsdk.R; @@ -31,27 +32,31 @@ import java.util.Map; */ public class MarkerViewManager { - private Map<MarkerView, View> markerViewMap; + private final ViewGroup markerViewContainer; + private final Map<MarkerView, View> markerViewMap = new HashMap<>(); + private final List<MapboxMap.MarkerViewAdapter> markerViewAdapters = new ArrayList<>(); + + // TODO refactor MapboxMap out for Projection and Transform + // Requires removing MapboxMap from Annotations by using Peer model from #6912 private MapboxMap mapboxMap; - private MapView mapView; - private List<MapboxMap.MarkerViewAdapter> markerViewAdapters; + private long viewMarkerBoundsUpdateTime; private MapboxMap.OnMarkerViewClickListener onMarkerViewClickListener; - private ImageMarkerViewAdapter defaultMarkerViewAdapter; /** * Creates an instance of MarkerViewManager. * - * @param mapboxMap the MapboxMap associated with the MarkerViewManager - * @param mapView the MapView associated with the MarkerViewManager + * @param container the ViewGroup associated with the MarkerViewManager */ - public MarkerViewManager(@NonNull MapboxMap mapboxMap, @NonNull MapView mapView) { + public MarkerViewManager(@NonNull ViewGroup container) { + this.markerViewContainer = container; + this.markerViewAdapters.add(new ImageMarkerViewAdapter(container.getContext())); + } + + // TODO refactor MapboxMap out for Projection and Transform + // Requires removing MapboxMap from Annotations by using Peer model from #6912 + public void bind(MapboxMap mapboxMap){ this.mapboxMap = mapboxMap; - this.markerViewAdapters = new ArrayList<>(); - this.mapView = mapView; - this.markerViewMap = new HashMap<>(); - this.defaultMarkerViewAdapter = new ImageMarkerViewAdapter(mapView.getContext()); - this.markerViewAdapters.add(defaultMarkerViewAdapter); } /** @@ -415,7 +420,7 @@ public class MarkerViewManager { * </p> */ public void invalidateViewMarkersInVisibleRegion() { - RectF mapViewRect = new RectF(0, 0, mapView.getWidth(), mapView.getHeight()); + RectF mapViewRect = new RectF(0, 0, markerViewContainer.getWidth(), markerViewContainer.getHeight()); List<MarkerView> markers = mapboxMap.getMarkerViewsInRect(mapViewRect); View convertView; @@ -445,7 +450,7 @@ public class MarkerViewManager { // Inflate View convertView = (View) adapter.getViewReusePool().acquire(); - final View adaptedView = adapter.getView(marker, convertView, mapView); + final View adaptedView = adapter.getView(marker, convertView, markerViewContainer); if (adaptedView != null) { adaptedView.setRotationX(marker.getTilt()); adaptedView.setRotation(marker.getRotation()); @@ -464,7 +469,7 @@ public class MarkerViewManager { markerViewMap.put(marker, adaptedView); if (convertView == null) { adaptedView.setVisibility(View.GONE); - mapView.getMarkerViewContainer().addView(adaptedView); + markerViewContainer.addView(adaptedView); } } } @@ -515,7 +520,7 @@ public class MarkerViewManager { for (final MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) { if (adapter.getMarkerClass().equals(marker.getClass())) { View convertView = (View) adapter.getViewReusePool().acquire(); - view = adapter.getView(marker, convertView, mapView); + view = adapter.getView(marker, convertView, markerViewContainer); break; } } @@ -523,7 +528,7 @@ public class MarkerViewManager { if (view != null) { if (marker.getWidth() == 0) { - if(view.getMeasuredWidth()==0) { + if (view.getMeasuredWidth() == 0) { //Ensure the marker's view is measured first view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); } @@ -546,6 +551,10 @@ public class MarkerViewManager { } } + public ViewGroup getMarkerViewContainer() { + return markerViewContainer; + } + /** * Default MarkerViewAdapter used for base class of {@link MarkerView} to adapt a MarkerView to * an ImageView. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java index 9c96450a4c..04105ebc59 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java @@ -257,7 +257,7 @@ public final class CameraUpdateFactory { float scaleX = (uiSettings.getWidth() - padding.left - padding.right) / width; float scaleY = (uiSettings.getHeight() - padding.top - padding.bottom) / height; minScale = scaleX < scaleY ? scaleX : scaleY; - zoom = projection.calculateZoom(minScale); + zoom = calculateZoom(mapboxMap, minScale); zoom = MathUtils.clamp(zoom, mapboxMap.getMinZoom(), mapboxMap.getMaxZoom()); } @@ -275,6 +275,16 @@ public final class CameraUpdateFactory { .bearing(0) .build(); } + + /** + * Calculates a zoom level based on minimum scale and current scale from MapView + * + * @param minScale The minimum scale to calculate the zoom level. + * @return zoom level that fits the MapView. + */ + public double calculateZoom(MapboxMap mapboxMap, float minScale) { + return Math.log(mapboxMap.getCameraPosition().zoom * minScale) / Math.log(2); + } } static final class CameraMoveUpdate implements CameraUpdate { 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 9e08d93bfc..c1cf966eed 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 @@ -1,9 +1,11 @@ package com.mapbox.mapboxsdk.maps; +import android.graphics.PointF; import android.graphics.RectF; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; +import android.view.ViewGroup; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; @@ -18,6 +20,7 @@ import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -32,30 +35,38 @@ import java.util.List; */ class AnnotationManager implements MapView.OnMapChangedListener { - private NativeMapView nativeMapView; - private IconManager iconManager; - private InfoWindowManager infoWindowManager; - private MarkerViewManager markerViewManager; + private final NativeMapView nativeMapView; + private final MapView mapView; + private final IconManager iconManager; + private final InfoWindowManager infoWindowManager = new InfoWindowManager(); + private final MarkerViewManager markerViewManager; + private final LongSparseArray<Annotation> annotations = new LongSparseArray<>(); + private final List<Marker> selectedMarkers = new ArrayList<>(); - private LongSparseArray<Annotation> annotations; - private List<Marker> selectedMarkers; + private MapboxMap mapboxMap; private MapboxMap.OnMarkerClickListener onMarkerClickListener; private boolean isWaitingForRenderInvoke; - AnnotationManager(NativeMapView view, IconManager iconManager, InfoWindowManager manager) { + AnnotationManager(NativeMapView view, MapView mapView, MarkerViewManager markerViewManager) { this.nativeMapView = view; - this.iconManager = iconManager; - this.infoWindowManager = manager; - this.selectedMarkers = new ArrayList<>(); - this.annotations = new LongSparseArray<>(); - + this.mapView = mapView; + this.iconManager = new IconManager(nativeMapView); + this.markerViewManager = markerViewManager; if (view != null) { // null checking needed for unit tests view.addOnMapChangedListener(this); } } + // TODO refactor MapboxMap out for Projection and Transform + // Requires removing MapboxMap from Annotations by using Peer model from #6912 + AnnotationManager bind(MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + this.markerViewManager.bind(mapboxMap); + return this; + } + @Override public void onMapChanged(@MapView.MapChange int change) { if (isWaitingForRenderInvoke && change == MapView.DID_FINISH_RENDERING_FRAME_FULLY_RENDERED) { @@ -280,7 +291,7 @@ class AnnotationManager implements MapView.OnMapChangedListener { onMarkerClickListener = listener; } - void selectMarker(@NonNull Marker marker, @NonNull MapboxMap mapboxMap) { + void selectMarker(@NonNull Marker marker) { if (selectedMarkers.contains(marker)) { return; } @@ -303,7 +314,7 @@ class AnnotationManager implements MapView.OnMapChangedListener { } if (infoWindowManager.isInfoWindowValidForMarker(marker) || infoWindowManager.getInfoWindowAdapter() != null) { - infoWindowManager.add(marker.showInfoWindow(mapboxMap, mapboxMap.getMapView())); + infoWindowManager.add(marker.showInfoWindow(mapboxMap, mapView)); } } @@ -564,14 +575,11 @@ class AnnotationManager implements MapView.OnMapChangedListener { return polylines; } - // - // MarkerViewManager - // + InfoWindowManager getInfoWindowManager() { + return infoWindowManager; + } - MarkerViewManager getMarkerViewManager(MapboxMap mapboxMap) { - if (markerViewManager == null) { - this.markerViewManager = new MarkerViewManager(mapboxMap, mapboxMap.getMapView()); - } + MarkerViewManager getMarkerViewManager() { return markerViewManager; } @@ -589,12 +597,13 @@ class AnnotationManager implements MapView.OnMapChangedListener { for (Marker marker : selectedMarkers) { if (marker.isInfoWindowShown()) { marker.hideInfoWindow(); - marker.showInfoWindow(mapboxMap, mapboxMap.getMapView()); + marker.showInfoWindow(mapboxMap, mapView); } } } void reloadMarkers() { + iconManager.reloadIcons(); int count = annotations.size(); for (int i = 0; i < count; i++) { Annotation annotation = annotations.get(i); @@ -606,4 +615,58 @@ class AnnotationManager implements MapView.OnMapChangedListener { } } } + + // + // Click event + // + + boolean onTap(PointF tapPoint, float screenDensity) { + float toleranceSides = 4 * screenDensity; + float toleranceTopBottom = 10 * screenDensity; + + RectF tapRect = new RectF(tapPoint.x - iconManager.getAverageIconWidth() / 2 - toleranceSides, + tapPoint.y - iconManager.getAverageIconHeight() / 2 - toleranceTopBottom, + tapPoint.x + iconManager.getAverageIconWidth() / 2 + toleranceSides, + tapPoint.y + iconManager.getAverageIconHeight() / 2 + toleranceTopBottom); + + List<Marker> nearbyMarkers = getMarkersInRect(tapRect); + long newSelectedMarkerId = -1; + + if (nearbyMarkers != null && nearbyMarkers.size() > 0) { + Collections.sort(nearbyMarkers); + for (Marker nearbyMarker : nearbyMarkers) { + boolean found = false; + for (Marker selectedMarker : selectedMarkers) { + if (selectedMarker.equals(nearbyMarker)) { + found = true; + } + } + if (!found) { + newSelectedMarkerId = nearbyMarker.getId(); + break; + } + } + } + + if (newSelectedMarkerId >= 0) { + List<Annotation> annotations = getAnnotations(); + int count = annotations.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotations.get(i); + if (annotation instanceof Marker) { + if (annotation.getId() == newSelectedMarkerId) { + if (selectedMarkers.isEmpty() || !selectedMarkers.contains(annotation)) { + if (!(annotation instanceof MarkerView)) { + selectMarker((Marker) annotation); + } else { + markerViewManager.onClickMarkerView((MarkerView) annotation); + } + } + return true; + } + } + } + } + return false; + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassViewSettings.java deleted file mode 100644 index 3e1b14d641..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassViewSettings.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.mapbox.mapboxsdk.maps; - -/** - * Settings for the overlain views of a MapboxMap. Used by UiSettings. - */ -class CompassViewSettings extends ViewSettings{ - - private boolean fadeFacingNorth = true; - - public CompassViewSettings() { - super(); - } - - public boolean isFadeFacingNorth() { - return fadeFacingNorth; - } - - public void setFadeFacingNorth(boolean fadeFacingNorth) { - this.fadeFacingNorth = fadeFacingNorth; - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/FocalPointChangeListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/FocalPointChangeListener.java new file mode 100644 index 0000000000..5f20155119 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/FocalPointChangeListener.java @@ -0,0 +1,8 @@ +package com.mapbox.mapboxsdk.maps; + +import android.graphics.PointF; + +public interface FocalPointChangeListener { + + void onFocalPointChanged(PointF pointF); +} 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 new file mode 100644 index 0000000000..0df5491535 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -0,0 +1,601 @@ +package com.mapbox.mapboxsdk.maps; + +import android.content.Context; +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.support.v4.view.GestureDetectorCompat; +import android.support.v4.view.ScaleGestureDetectorCompat; +import android.util.Log; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.ViewConfiguration; + +import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; +import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; +import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; +import com.mapbox.mapboxsdk.constants.MapboxConstants; +import com.mapbox.mapboxsdk.telemetry.MapboxEvent; + +import timber.log.Timber; + +/** + * Manages gestures events on a MapView. + * <p> + * Relies on gesture detection code in almeros.android.multitouch.gesturedetectors. + * </p> + */ +final class MapGestureDetector { + + private final Transform transform; + private final Projection projection; + private final UiSettings uiSettings; + private final TrackingSettings trackingSettings; + private final AnnotationManager annotationManager; + + private final GestureDetectorCompat gestureDetector; + private final ScaleGestureDetector scaleGestureDetector; + private final RotateGestureDetector rotateGestureDetector; + private final ShoveGestureDetector shoveGestureDetector; + + private MapboxMap.OnMapClickListener onMapClickListener; + private MapboxMap.OnMapLongClickListener onMapLongClickListener; + private MapboxMap.OnFlingListener onFlingListener; + private MapboxMap.OnScrollListener onScrollListener; + + private PointF focalPoint; + + private boolean twoTap = false; + private boolean zoomStarted = false; + private boolean dragStarted = false; + private boolean quickZoom = false; + private boolean scrollInProgress = false; + + MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings, + TrackingSettings trackingSettings, AnnotationManager annotationManager) { + this.annotationManager = annotationManager; + this.transform = transform; + this.projection = projection; + this.uiSettings = uiSettings; + this.trackingSettings = trackingSettings; + + // Touch gesture detectors + gestureDetector = new GestureDetectorCompat(context, new GestureListener()); + gestureDetector.setIsLongpressEnabled(true); + scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); + ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true); + rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener()); + shoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener()); + } + + /** + * Set the gesture focal point. + * <p> + * this is the center point used for calculate transformations from gestures, value is + * overridden if end user provides his own through {@link UiSettings#setFocalPoint(PointF)}. + * </p> + * + * @param focalPoint the center point for gestures + */ + void setFocalPoint(PointF focalPoint) { + if (focalPoint == null) { + // resetting focal point, + if (uiSettings.getFocalPoint() != null) { + // using user provided one to reset + focalPoint = uiSettings.getFocalPoint(); + } + } + this.focalPoint = focalPoint; + } + + + /** + * Called when user touches the screen, all positions are absolute. + * <p> + * Forwards event to the related gesture detectors. + * </p> + * + * @param event the MotionEvent + * @return True if touch event is handled + */ + boolean onTouchEvent(@NonNull MotionEvent event) { + // Check and ignore non touch or left clicks + if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { + return false; + } + + // Check two finger gestures first + rotateGestureDetector.onTouchEvent(event); + scaleGestureDetector.onTouchEvent(event); + shoveGestureDetector.onTouchEvent(event); + + // Handle two finger tap + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + // First pointer down + transform.setGestureInProgress(true); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + // Second pointer down + twoTap = event.getPointerCount() == 2 + && uiSettings.isZoomGesturesEnabled(); + if (twoTap) { + // Confirmed 2nd Finger Down + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_TWO_FINGER_SINGLETAP, event.getX(), event.getY(), transform.getZoom()); + } + break; + + case MotionEvent.ACTION_POINTER_UP: + // Second pointer up + break; + + case MotionEvent.ACTION_UP: + // First pointer up + long tapInterval = event.getEventTime() - event.getDownTime(); + boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); + boolean inProgress = rotateGestureDetector.isInProgress() + || scaleGestureDetector.isInProgress() + || shoveGestureDetector.isInProgress(); + + if (twoTap && isTap && !inProgress) { + if (focalPoint != null) { + transform.zoom(false, focalPoint.x, focalPoint.y); + } else { + PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event); + transform.zoom(false, focalPoint.x, focalPoint.y); + } + twoTap = false; + return true; + } + + // Scroll / Pan Has Stopped + if (scrollInProgress) { + MapboxEvent.trackGestureDragEndEvent(projection, event.getX(), event.getY(), transform.getZoom()); + scrollInProgress = false; + } + + twoTap = false; + transform.setGestureInProgress(false); + break; + + case MotionEvent.ACTION_CANCEL: + twoTap = false; + transform.setGestureInProgress(false); + break; + } + + return gestureDetector.onTouchEvent(event); + } + + /** + * Called for events that don't fit the other handlers. + * <p> + * Examples of such events are mouse scroll events, mouse moves, joystick & trackpad. + * </p> + * + * @param event The MotionEvent occured + * @return True is the event is handled + */ + boolean onGenericMotionEvent(MotionEvent event) { + // Mouse events + //if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18 + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == InputDevice.SOURCE_CLASS_POINTER) { + // Choose the action + switch (event.getActionMasked()) { + // Mouse scrolls + case MotionEvent.ACTION_SCROLL: + if (!uiSettings.isZoomGesturesEnabled()) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Get the vertical scroll amount, one click = 1 + float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + + // Scale the map by the appropriate power of two factor + transform.zoomBy(Math.pow(2.0, scrollDist), event.getX(), event.getY()); + + return true; + + default: + // We are not interested in this event + return false; + } + } + + // We are not interested in this event + return false; + } + + + /** + * Responsible for handling one finger gestures. + */ + private class GestureListener extends android.view.GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onDown(MotionEvent event) { + return true; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + if (!uiSettings.isZoomGesturesEnabled()) { + return false; + } + + switch (e.getAction()) { + case MotionEvent.ACTION_DOWN: + break; + case MotionEvent.ACTION_MOVE: + break; + case MotionEvent.ACTION_UP: + if (quickZoom) { + // insert here? + quickZoom = false; + break; + } + + // Single finger double tap + if (focalPoint != null) { + // User provided focal point + transform.zoom(true, focalPoint.x, focalPoint.y); + } else { + // Zoom in on gesture + transform.zoom(true, e.getX(), e.getY()); + } + break; + } + + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_DOUBLETAP, e.getX(), e.getY(), transform.getZoom()); + + return true; + } + + @Override + public boolean onSingleTapUp(MotionEvent motionEvent) { + // Cancel any animation + transform.cancelTransitions(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent motionEvent) { + PointF tapPoint = new PointF(motionEvent.getX(), motionEvent.getY()); + boolean tapHandled = annotationManager.onTap(tapPoint, uiSettings.getPixelRatio()); + + if (!tapHandled) { + if (uiSettings.isDeselectMarkersOnTap()) { + // deselect any selected marker + annotationManager.deselectMarkers(); + } + + // notify app of map click + if (onMapClickListener != null) { + onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint)); + } + } + + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_SINGLETAP, motionEvent.getX(), motionEvent.getY(), transform.getZoom()); + return true; + } + + @Override + public void onLongPress(MotionEvent motionEvent) { + if (onMapLongClickListener != null && !quickZoom) { + onMapLongClickListener.onMapLongClick(projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY()))); + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + trackingSettings.resetTrackingModesIfRequired(true, false); + + double decelerationRate = 1; + + // Cancel any animation + transform.cancelTransitions(); + + float screenDensity = uiSettings.getPixelRatio(); + double offsetX = velocityX * decelerationRate / 4 / screenDensity; + double offsetY = velocityY * decelerationRate / 4 / screenDensity; + + transform.setGestureInProgress(true); + transform.moveBy(offsetX, offsetY, (long) (decelerationRate * 1000.0f)); + transform.setGestureInProgress(false); + + if (onFlingListener != null) { + onFlingListener.onFling(); + } + + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_PAN_START, e1.getX(), e1.getY(), transform.getZoom()); + return true; + } + + // Called for drags + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (!scrollInProgress) { + scrollInProgress = true; + } + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + if (dragStarted) { + return false; + } + + // reset tracking if needed + trackingSettings.resetTrackingModesIfRequired(true, false); + // Cancel any animation + transform.cancelTransitions(); + + // Scroll the map + transform.moveBy(-distanceX, -distanceY, 0 /*no duration*/); + + if (onScrollListener != null) { + onScrollListener.onScroll(); + } + return true; + } + } + + /** + * Responsible for handling two finger gestures and double-tap drag gestures. + */ + private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + long beginTime = 0; + float scaleFactor = 1.0f; + + // Called when two fingers first touch the screen + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + if (!uiSettings.isZoomGesturesEnabled()) { + return false; + } + + beginTime = detector.getEventTime(); + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_PINCH_START, detector.getFocusX(), detector.getFocusY(), transform.getZoom()); + return true; + } + + // Called when fingers leave screen + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + beginTime = 0; + scaleFactor = 1.0f; + zoomStarted = false; + } + + // Called each time a finger moves + // Called for pinch zooms and quickzooms/quickscales + @Override + public boolean onScale(ScaleGestureDetector detector) { + if (!uiSettings.isZoomGesturesEnabled()) { + return super.onScale(detector); + } + + // If scale is large enough ignore a tap + scaleFactor *= detector.getScaleFactor(); + if ((scaleFactor > 1.05f) || (scaleFactor < 0.95f)) { + zoomStarted = true; + } + + // Ignore short touches in case it is a tap + // Also ignore small scales + long time = detector.getEventTime(); + long interval = time - beginTime; + if (!zoomStarted && (interval <= ViewConfiguration.getTapTimeout())) { + return false; + } + + if (!zoomStarted) { + return false; + } + + if (dragStarted) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Gesture is a quickzoom if there aren't two fingers + quickZoom = !twoTap; + + // 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); + // Scale the map + if (focalPoint != null) { + // arround user provided focal point + transform.zoomBy(detector.getScaleFactor(), focalPoint.x, focalPoint.y); + } else if (quickZoom) { + // around center map + transform.zoomBy(detector.getScaleFactor(), uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + } else { + // around gesture + transform.zoomBy(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY()); + } + + return true; + } + } + + /** + * Responsible for handling rotation gestures. + */ + private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { + + long beginTime = 0; + float totalAngle = 0.0f; + boolean started = false; + + // Called when two fingers first touch the screen + @Override + public boolean onRotateBegin(RotateGestureDetector detector) { + if (!trackingSettings.isRotateGestureCurrentlyEnabled()) { + return false; + } + + beginTime = detector.getEventTime(); + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_ROTATION_START, detector.getFocusX(), detector.getFocusY(), transform.getZoom()); + return true; + } + + // Called when the fingers leave the screen + @Override + public void onRotateEnd(RotateGestureDetector detector) { + beginTime = 0; + totalAngle = 0.0f; + started = false; + } + + // Called each time one of the two fingers moves + // Called for rotation + @Override + public boolean onRotate(RotateGestureDetector detector) { + if (!trackingSettings.isRotateGestureCurrentlyEnabled() || dragStarted) { + return false; + } + + // If rotate is large enough ignore a tap + // Also is zoom already started, don't rotate + totalAngle += detector.getRotationDegreesDelta(); + if (!zoomStarted && ((totalAngle > 20.0f) || (totalAngle < -20.0f))) { + started = true; + } + + // 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())) { + return false; + } + + if (!started) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // rotation constitutes translation of anything except the center of + // rotation, so cancel both location and bearing tracking if required + + trackingSettings.resetTrackingModesIfRequired(true, true); + + // Get rotate value + double bearing = transform.getRawBearing(); + bearing += detector.getRotationDegreesDelta(); + + // Rotate the map + if (focalPoint != null) { + // User provided focal point + transform.setBearing(bearing, focalPoint.x, focalPoint.y); + } else { + // around gesture + transform.setBearing(bearing, detector.getFocusX(), detector.getFocusY()); + } + return true; + } + } + + /** + * Responsible for handling 2 finger shove gestures. + */ + private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener { + + long beginTime = 0; + float totalDelta = 0.0f; + boolean started = false; + + @Override + public boolean onShoveBegin(ShoveGestureDetector detector) { + if (!uiSettings.isTiltGesturesEnabled()) { + return false; + } + + beginTime = detector.getEventTime(); + MapboxEvent.trackGestureEvent(projection, MapboxEvent.GESTURE_PITCH_START, detector.getFocusX(), detector.getFocusY(), transform.getZoom()); + return true; + } + + @Override + public void onShoveEnd(ShoveGestureDetector detector) { + beginTime = 0; + totalDelta = 0.0f; + started = false; + dragStarted = false; + } + + @Override + public boolean onShove(ShoveGestureDetector detector) { + if (!uiSettings.isTiltGesturesEnabled()) { + return false; + } + + // If tilt is large enough ignore a tap + // Also if zoom already started, don't tilt + totalDelta += detector.getShovePixelsDelta(); + if (!zoomStarted && ((totalDelta > 10.0f) || (totalDelta < -10.0f))) { + started = true; + } + + // Ignore short touches in case it is a tap + // Also ignore small tilt + long time = detector.getEventTime(); + long interval = time - beginTime; + if (!started && (interval <= ViewConfiguration.getTapTimeout())) { + return false; + } + + if (!started) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Get tilt value (scale and clamp) + double pitch = transform.getTilt(); + pitch -= 0.1 * detector.getShovePixelsDelta(); + pitch = Math.max(MapboxConstants.MINIMUM_TILT, Math.min(MapboxConstants.MAXIMUM_TILT, pitch)); + + // Tilt the map + transform.setTilt(pitch); + + dragStarted = true; + + return true; + } + } + + void setOnMapClickListener(MapboxMap.OnMapClickListener onMapClickListener) { + this.onMapClickListener = onMapClickListener; + } + + void setOnMapLongClickListener(MapboxMap.OnMapLongClickListener onMapLongClickListener) { + this.onMapLongClickListener = onMapLongClickListener; + } + + void setOnFlingListener(MapboxMap.OnFlingListener onFlingListener) { + this.onFlingListener = onFlingListener; + } + + void setOnScrollListener(MapboxMap.OnScrollListener onScrollListener) { + this.onScrollListener = onScrollListener; + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java new file mode 100644 index 0000000000..09b4a2c9ca --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java @@ -0,0 +1,264 @@ +package com.mapbox.mapboxsdk.maps; + +import android.os.Handler; +import android.support.annotation.NonNull; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +/** + * Manages key events on a MapView. + * <p> + * <ul> + * <li> Uses {@link Transform} to change the map state</li> + * <li> Uses {@link TrackingSettings} to verify validity of the current tracking mode.</li> + * <li> Uses {@link UiSettings} to verify validity of user restricted movement.</li> + * </ul> + * <p> + */ +final class MapKeyListener { + + private final TrackingSettings trackingSettings; + private final Transform transform; + private final UiSettings uiSettings; + + private TrackballLongPressTimeOut currentTrackballLongPressTimeOut; + + MapKeyListener(@NonNull Transform transform, @NonNull TrackingSettings trackingSettings, @NonNull UiSettings uiSettings) { + this.transform = transform; + this.trackingSettings = trackingSettings; + this.uiSettings = uiSettings; + } + + /** + * Called when the user presses a key, alse called for repeated keys held down. + * + * @param keyCode the id of the pressed key + * @param event the related key event + * @return true if the wevent is handled + */ + boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { + // If the user has held the scroll key down for a while then accelerate + // the scroll speed + double scrollDist = event.getRepeatCount() >= 5 ? 50.0 : 10.0; + + // Check which key was pressed via hardware/real key code + switch (keyCode) { + // Tell the system to track these keys for long presses on + // onKeyLongPress is fired + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + event.startTracking(); + return true; + + case KeyEvent.KEYCODE_DPAD_LEFT: + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Move left + transform.moveBy(scrollDist, 0.0, 0 /*no animation*/); + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Move right + transform.moveBy(-scrollDist, 0.0, 0 /*no animation*/); + return true; + + case KeyEvent.KEYCODE_DPAD_UP: + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Move up + transform.moveBy(0.0, scrollDist, 0 /*no animation*/); + return true; + + case KeyEvent.KEYCODE_DPAD_DOWN: + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Move down + transform.moveBy(0.0, -scrollDist, 0 /*no animation*/); + return true; + + default: + // We are not interested in this key + return false; + } + } + + /** + * Called when the user long presses a key that is being tracked. + * + * @param keyCode the id of the long pressed key + * @param event the related key event + * @return true if event is handled + */ + boolean onKeyLongPress(int keyCode, KeyEvent event) { + // Check which key was pressed via hardware/real key code + switch (keyCode) { + // Tell the system to track these keys for long presses on + // onKeyLongPress is fired + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (!uiSettings.isZoomGesturesEnabled()) { + return false; + } + + // Zoom out + transform.zoom(false, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + return true; + + default: + // We are not interested in this key + return false; + } + } + + /** + * Called when the user releases a key. + * + * @param keyCode the id of the released key + * @param event the related key event + * @return true if the event is handled + */ + boolean onKeyUp(int keyCode, KeyEvent event) { + // Check if the key action was canceled (used for virtual keyboards) + if (event.isCanceled()) { + return false; + } + + // Check which key was pressed via hardware/real key code + // Note if keyboard does not have physical key (ie primary non-shifted + // key) then it will not appear here + // Must use the key character map as physical to character is not + // fixed/guaranteed + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (!uiSettings.isZoomGesturesEnabled()) { + return false; + } + + // Zoom in + transform.zoom(true, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + return true; + } + + // We are not interested in this key + return false; + } + + /** + * Called for trackball events, all motions are relative in device specific units. + * + * @param event the related motion event + * @return true if the event is handled + */ + boolean onTrackballEvent(MotionEvent event) { + // Choose the action + switch (event.getActionMasked()) { + // The trackball was rotated + case MotionEvent.ACTION_MOVE: + if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { + return false; + } + + // Cancel any animation + transform.cancelTransitions(); + + // Scroll the map + transform.moveBy(-10.0 * event.getX(), -10.0 * event.getY(), 0 /*no animation*/); + return true; + + // Trackball was pushed in so start tracking and tell system we are + // interested + // We will then get the up action + case MotionEvent.ACTION_DOWN: + // Set up a delayed callback to check if trackball is still + // After waiting the system long press time out + if (currentTrackballLongPressTimeOut != null) { + currentTrackballLongPressTimeOut.cancel(); + currentTrackballLongPressTimeOut = null; + } + currentTrackballLongPressTimeOut = new TrackballLongPressTimeOut(); + new Handler().postDelayed(currentTrackballLongPressTimeOut, + ViewConfiguration.getLongPressTimeout()); + return true; + + // Trackball was released + case MotionEvent.ACTION_UP: + if (!uiSettings.isZoomGesturesEnabled()) { + return false; + } + + // Only handle if we have not already long pressed + if (currentTrackballLongPressTimeOut != null) { + // Zoom in + transform.zoom(true, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + } + return true; + + // Trackball was cancelled + case MotionEvent.ACTION_CANCEL: + if (currentTrackballLongPressTimeOut != null) { + currentTrackballLongPressTimeOut.cancel(); + currentTrackballLongPressTimeOut = null; + } + return true; + + default: + // We are not interested in this event + return false; + } + } + + /** + * This class implements the trackball long press time out callback + */ + private class TrackballLongPressTimeOut implements Runnable { + + // Track if we have been cancelled + private boolean cancelled; + + TrackballLongPressTimeOut() { + cancelled = false; + } + + // Cancel the timeout + public void cancel() { + cancelled = true; + } + + // Called when long press time out expires + @Override + public void run() { + // Check if the trackball is still pressed + if (!cancelled) { + // Zoom out + transform.zoom(false, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + + // Ensure the up action is not run + currentTrackballLongPressTimeOut = null; + } + } + } +} 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 3fe9a67f3c..cd7caf395d 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 @@ -1,7 +1,5 @@ package com.mapbox.mapboxsdk.maps; -import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.app.Fragment; @@ -11,66 +9,47 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.PointF; -import android.graphics.RectF; import android.graphics.SurfaceTexture; -import android.location.Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Bundle; import android.support.annotation.CallSuper; -import android.support.annotation.FloatRange; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.GestureDetectorCompat; -import android.support.v4.view.ScaleGestureDetectorCompat; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.AttributeSet; -import timber.log.Timber; -import android.view.GestureDetector; -import android.view.InputDevice; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.ScaleGestureDetector; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; -import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; -import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; -import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; import com.mapbox.mapboxsdk.MapboxAccountManager; import com.mapbox.mapboxsdk.R; -import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.InfoWindow; -import com.mapbox.mapboxsdk.annotations.Marker; -import com.mapbox.mapboxsdk.annotations.MarkerView; +import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.constants.Style; -import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.location.LocationListener; import com.mapbox.mapboxsdk.location.LocationServices; import com.mapbox.mapboxsdk.maps.widgets.CompassView; @@ -78,17 +57,15 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.telemetry.MapboxEvent; import com.mapbox.mapboxsdk.telemetry.MapboxEventManager; -import com.mapbox.mapboxsdk.utils.ColorUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.List; -import static com.mapbox.mapboxsdk.utils.MathUtils.convertNativeBearing; +import timber.log.Timber; /** * <p> @@ -107,8 +84,6 @@ import static com.mapbox.mapboxsdk.utils.MathUtils.convertNativeBearing; public class MapView extends FrameLayout { private MapboxMap mapboxMap; - private IconManager iconManager; - private AnnotationManager annotationManager; private boolean initialLoad; private boolean destroyed; @@ -116,42 +91,18 @@ public class MapView extends FrameLayout { private NativeMapView nativeMapView; private boolean hasSurface = false; - private ViewGroup markerViewContainer; private CompassView compassView; - private ImageView logoView; - private ImageView attributionsView; private MyLocationView myLocationView; private LocationListener myLocationListener; - private Projection projection; + private MapGestureDetector mapGestureDetector; + private MapKeyListener mapKeyListener; - private ZoomButtonsController zoomButtonsController; private ConnectivityReceiver connectivityReceiver; - private float screenDensity = 1.0f; - - private TrackballLongPressTimeOut currentTrackballLongPressTimeOut; - private GestureDetectorCompat gestureDetector; - private ScaleGestureDetector scaleGestureDetector; - private RotateGestureDetector rotateGestureDetector; - private ShoveGestureDetector shoveGestureDetector; - private boolean twoTap = false; - private boolean zoomStarted = false; - private boolean dragStarted = false; - private boolean quickZoom = false; - private boolean scrollInProgress = false; - - private int contentPaddingLeft; - private int contentPaddingTop; - private int contentPaddingRight; - private int contentPaddingBottom; - - private PointF focalPoint; - - private String styleUrl = Style.MAPBOX_STREETS; - private boolean styleWasSet = false; - - private List<OnMapReadyCallback> onMapReadyCallbackList; + + private List<OnMapReadyCallback> onMapReadyCallbackList = new ArrayList<>(); private SnapshotRequest snapshotRequest; + private ZoomButtonsController zoomButtonsController; private boolean onStartCalled; private boolean onStopCalled; @@ -175,21 +126,20 @@ public class MapView extends FrameLayout { } @UiThread - public MapView(@NonNull Context context, @Nullable MapboxMapOptions options) { + public MapView(@NonNull Context context, @NonNull MapboxMapOptions options) { super(context); initialize(context, options); } - private void initialize(@NonNull Context context, @NonNull MapboxMapOptions options) { + private void initialize(@NonNull final Context context, @NonNull final MapboxMapOptions options) { if (isInEditMode()) { // if we are in an editor mode we show an image of a map LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_preview, this); return; } + // TODO distill into singular purpose methods/classes initialLoad = true; - onMapReadyCallbackList = new ArrayList<>(); - View view = LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_internal, this); setWillNotDraw(false); @@ -204,153 +154,61 @@ public class MapView extends FrameLayout { } nativeMapView = new NativeMapView(this); - iconManager = new IconManager(nativeMapView); - mapboxMap = new MapboxMap(this, iconManager); - annotationManager = mapboxMap.getAnnotationManager(); - projection = mapboxMap.getProjection(); + + // inflate overlain Views + compassView = (CompassView) view.findViewById(R.id.compassView); + myLocationView = (MyLocationView) view.findViewById(R.id.userLocationView); + ImageView logoView = (ImageView) view.findViewById(R.id.logoView); + ImageView attributionsView = (ImageView) view.findViewById(R.id.attributionView); + attributionsView.setOnClickListener(new AttributionOnClickListener(this)); + ViewGroup markerViewContainer = (ViewGroup) findViewById(R.id.markerViewContainer); + + // interface for focal point invalidation + FocalPointInvalidator focalPointInvalidator = new FocalPointInvalidator(); + + // interface for registering touch listeners + RegisterTouchListener registerTouchListener = new RegisterTouchListener(); + + // setup components for MapboxMap creation + Projection projection = new Projection(nativeMapView); + UiSettings uiSettings = new UiSettings(projection, focalPointInvalidator, compassView, attributionsView, logoView); + TrackingSettings trackingSettings = new TrackingSettings(myLocationView, uiSettings, focalPointInvalidator); + MyLocationViewSettings myLocationViewSettings = new MyLocationViewSettings(projection, myLocationView, trackingSettings); + MarkerViewManager markerViewManager = new MarkerViewManager(markerViewContainer); + AnnotationManager annotationManager = new AnnotationManager(nativeMapView, this, markerViewManager); + Transform transform = new Transform(nativeMapView, annotationManager.getMarkerViewManager(), trackingSettings); + mapboxMap = new MapboxMap(nativeMapView, transform, uiSettings, trackingSettings, myLocationViewSettings, projection, registerTouchListener, annotationManager); + + // active user input + mapGestureDetector = new MapGestureDetector(context, mapboxMap.getTransform(), projection, uiSettings, trackingSettings, annotationManager); + mapKeyListener = new MapKeyListener(mapboxMap.getTransform(), trackingSettings, uiSettings); + + // attach widgets to MapboxMap + compassView.setMapboxMap(mapboxMap); + myLocationView.setMapboxMap(mapboxMap); // Ensure this view is interactable setClickable(true); setLongClickable(true); setFocusable(true); setFocusableInTouchMode(true); + requestDisallowInterceptTouchEvent(true); requestFocus(); - // Touch gesture detectors - gestureDetector = new GestureDetectorCompat(context, new GestureListener()); - gestureDetector.setIsLongpressEnabled(true); - scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); - ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true); - rotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener()); - shoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener()); - - zoomButtonsController = new ZoomButtonsController(this); - zoomButtonsController.setZoomSpeed(MapboxConstants.ANIMATION_DURATION); - zoomButtonsController.setOnZoomListener(new OnZoomListener()); - // Connectivity onConnectivityChanged(isConnected()); - markerViewContainer = (ViewGroup) view.findViewById(R.id.markerViewContainer); - - myLocationView = (MyLocationView) view.findViewById(R.id.userLocationView); - myLocationView.setMapboxMap(mapboxMap); - - compassView = (CompassView) view.findViewById(R.id.compassView); - compassView.setMapboxMap(mapboxMap); - - logoView = (ImageView) view.findViewById(R.id.logoView); - - // Setup Attributions control - attributionsView = (ImageView) view.findViewById(R.id.attributionView); - attributionsView.setOnClickListener(new AttributionOnClickListener(this)); - - screenDensity = context.getResources().getDisplayMetrics().density; + // configure the zoom button controller + zoomButtonsController = new ZoomButtonsController(MapView.this); + zoomButtonsController.setZoomSpeed(MapboxConstants.ANIMATION_DURATION); + zoomButtonsController.setOnZoomListener(new OnZoomListener(mapboxMap)); - setInitialState(options); + mapboxMap.initialise(context, options); // Shows the zoom controls if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { - mapboxMap.getUiSettings().setZoomControlsEnabled(true); - } - } - - private void setInitialState(MapboxMapOptions options) { - mapboxMap.setDebugActive(options.getDebugActive()); - - CameraPosition position = options.getCamera(); - if (position != null && !position.equals(CameraPosition.DEFAULT)) { - mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(position)); - myLocationView.setTilt(position.tilt); - } - - // api base url - String apiBaseUrl = options.getApiBaseUrl(); - if (!TextUtils.isEmpty(apiBaseUrl)) { - setApiBaseUrl(apiBaseUrl); - } - - // access token - String accessToken = options.getAccessToken(); - if (!TextUtils.isEmpty(accessToken)) { - mapboxMap.setAccessToken(accessToken); - } - - // style url - String style = options.getStyle(); - if (!TextUtils.isEmpty(style)) { - styleUrl = style; - } - - // MyLocationView - MyLocationViewSettings myLocationViewSettings = mapboxMap.getMyLocationViewSettings(); - myLocationViewSettings.setForegroundDrawable( - options.getMyLocationForegroundDrawable(), options.getMyLocationForegroundBearingDrawable()); - myLocationViewSettings.setForegroundTintColor(options.getMyLocationForegroundTintColor()); - myLocationViewSettings.setBackgroundDrawable( - options.getMyLocationBackgroundDrawable(), options.getMyLocationBackgroundPadding()); - myLocationViewSettings.setBackgroundTintColor(options.getMyLocationBackgroundTintColor()); - myLocationViewSettings.setAccuracyAlpha(options.getMyLocationAccuracyAlpha()); - myLocationViewSettings.setAccuracyTintColor(options.getMyLocationAccuracyTintColor()); - mapboxMap.setMyLocationEnabled(options.getLocationEnabled()); - - // Enable gestures - UiSettings uiSettings = mapboxMap.getUiSettings(); - uiSettings.setZoomGesturesEnabled(options.getZoomGesturesEnabled()); - uiSettings.setZoomGestureChangeAllowed(options.getZoomGesturesEnabled()); - uiSettings.setScrollGesturesEnabled(options.getScrollGesturesEnabled()); - uiSettings.setScrollGestureChangeAllowed(options.getScrollGesturesEnabled()); - uiSettings.setRotateGesturesEnabled(options.getRotateGesturesEnabled()); - uiSettings.setRotateGestureChangeAllowed(options.getRotateGesturesEnabled()); - uiSettings.setTiltGesturesEnabled(options.getTiltGesturesEnabled()); - uiSettings.setTiltGestureChangeAllowed(options.getTiltGesturesEnabled()); - - // Ui Controls - uiSettings.setZoomControlsEnabled(options.getZoomControlsEnabled()); - - // Zoom - mapboxMap.setMaxZoom(options.getMaxZoom()); - mapboxMap.setMinZoom(options.getMinZoom()); - - // Compass - uiSettings.setCompassEnabled(options.getCompassEnabled()); - uiSettings.setCompassGravity(options.getCompassGravity()); - int[] compassMargins = options.getCompassMargins(); - if (compassMargins != null) { - uiSettings.setCompassMargins(compassMargins[0], compassMargins[1], compassMargins[2], compassMargins[3]); - } else { - int tenDp = (int) getResources().getDimension(R.dimen.mapbox_ten_dp); - uiSettings.setCompassMargins(tenDp, tenDp, tenDp, tenDp); - } - uiSettings.setCompassFadeFacingNorth(options.getCompassFadeFacingNorth()); - - // Logo - uiSettings.setLogoEnabled(options.getLogoEnabled()); - uiSettings.setLogoGravity(options.getLogoGravity()); - int[] logoMargins = options.getLogoMargins(); - if (logoMargins != null) { - uiSettings.setLogoMargins(logoMargins[0], logoMargins[1], logoMargins[2], logoMargins[3]); - } else { - int sixteenDp = (int) getResources().getDimension(R.dimen.mapbox_sixteen_dp); - uiSettings.setLogoMargins(sixteenDp, sixteenDp, sixteenDp, sixteenDp); - } - - // Attribution - uiSettings.setAttributionEnabled(options.getAttributionEnabled()); - uiSettings.setAttributionGravity(options.getAttributionGravity()); - int[] attributionMargins = options.getAttributionMargins(); - if (attributionMargins != null) { - uiSettings.setAttributionMargins(attributionMargins[0], attributionMargins[1], attributionMargins[2], attributionMargins[3]); - } else { - Resources resources = getResources(); - int sevenDp = (int) resources.getDimension(R.dimen.mapbox_seven_dp); - int seventySixDp = (int) resources.getDimension(R.dimen.mapbox_seventy_six_dp); - uiSettings.setAttributionMargins(seventySixDp, sevenDp, sevenDp, sevenDp); + uiSettings.setZoomControlsEnabled(true); } - - int attributionTintColor = options.getAttributionTintColor(); - uiSettings.setAttributionTintColor(attributionTintColor != -1 - ? attributionTintColor : ColorUtils.getPrimaryColor(getContext())); } // @@ -370,17 +228,20 @@ public class MapView extends FrameLayout { */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { + // TODO distill into singular purpose methods/classes String accessToken = mapboxMap.getAccessToken(); if (TextUtils.isEmpty(accessToken)) { accessToken = MapboxAccountManager.getInstance().getAccessToken(); - mapboxMap.setAccessToken(accessToken); + nativeMapView.setAccessToken(accessToken); } else { // user provided access token through xml attributes, need to start MapboxAccountManager MapboxAccountManager.start(getContext(), accessToken); + nativeMapView.setAccessToken(accessToken); } // Force a check for an access token MapboxAccountManager.validateAccessToken(accessToken); + nativeMapView.setAccessToken(accessToken); if (savedInstanceState != null && savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { @@ -427,7 +288,11 @@ public class MapView extends FrameLayout { , savedInstanceState.getInt(MapboxConstants.STATE_ATTRIBUTION_MARGIN_BOTTOM)); mapboxMap.setDebugActive(savedInstanceState.getBoolean(MapboxConstants.STATE_DEBUG_ACTIVE)); - styleUrl = savedInstanceState.getString(MapboxConstants.STATE_STYLE_URL); + + String styleUrl = savedInstanceState.getString(MapboxConstants.STATE_STYLE_URL); + if (!TextUtils.isEmpty(styleUrl)) { + nativeMapView.setStyleUrl(savedInstanceState.getString(MapboxConstants.STATE_STYLE_URL)); + } // User location try { @@ -462,11 +327,12 @@ public class MapView extends FrameLayout { addOnMapChangedListener(new OnMapChangedListener() { @Override public void onMapChanged(@MapChange int change) { + + // TODO extract logic into separate OnMapReady and Update Component if (change == DID_FINISH_LOADING_STYLE && initialLoad) { initialLoad = false; - iconManager.reloadIcons(); - annotationManager.reloadMarkers(); - annotationManager.adjustTopOffsetPixels(mapboxMap); + mapboxMap.getAnnotationManager().reloadMarkers(); + mapboxMap.getAnnotationManager().adjustTopOffsetPixels(mapboxMap); // Notify listeners the map is ready if (onMapReadyCallbackList.size() > 0) { @@ -484,7 +350,7 @@ public class MapView extends FrameLayout { } else if (change == REGION_IS_CHANGING || change == REGION_DID_CHANGE || change == DID_FINISH_LOADING_MAP) { mapboxMap.getMarkerViewManager().scheduleViewMarkerInvalidation(); - compassView.update(getDirection()); + compassView.update(mapboxMap.getTransform().getBearing()); myLocationView.update(); mapboxMap.getMarkerViewManager().update(); @@ -517,7 +383,7 @@ public class MapView extends FrameLayout { outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); outState.putParcelable(MapboxConstants.STATE_CAMERA_POSITION, mapboxMap.getCameraPosition()); outState.putBoolean(MapboxConstants.STATE_DEBUG_ACTIVE, mapboxMap.isDebugActive()); - outState.putString(MapboxConstants.STATE_STYLE_URL, styleUrl); + outState.putString(MapboxConstants.STATE_STYLE_URL, nativeMapView.getStyleUrl()); outState.putBoolean(MapboxConstants.STATE_MY_LOCATION_ENABLED, mapboxMap.isMyLocationEnabled()); // TrackingSettings @@ -579,9 +445,9 @@ public class MapView extends FrameLayout { nativeMapView.update(); myLocationView.onStart(); - // In case that no style was set or was loaded through MapboxMapOptions - if (!styleWasSet) { - setStyleUrl(styleUrl); + if (TextUtils.isEmpty(nativeMapView.getStyleUrl())) { + // if user hasn't loaded a Style yet, load default for them instead + nativeMapView.setStyleUrl(Style.MAPBOX_STREETS); } } @@ -640,16 +506,63 @@ public class MapView extends FrameLayout { nativeMapView = null; } - void setFocalPoint(PointF focalPoint) { - if (focalPoint == null) { - // resetting focal point, - UiSettings uiSettings = mapboxMap.getUiSettings(); - // need to validate if we need to reset focal point with user provided one - if (uiSettings.getFocalPoint() != null) { - focalPoint = uiSettings.getFocalPoint(); + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (mapboxMap.getUiSettings().isZoomControlsEnabled()) { + zoomButtonsController.setVisible(true); } } - this.focalPoint = focalPoint; + return mapGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mapKeyListener.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return mapKeyListener.onKeyLongPress(keyCode, event) || super.onKeyLongPress(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return mapKeyListener.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + return mapKeyListener.onTrackballEvent(event) || super.onTrackballEvent(event); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return mapGestureDetector.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + // Show the zoom controls + if (mapboxMap.getUiSettings().isZoomControlsEnabled()) { + zoomButtonsController.setVisible(true); + } + return true; + + case MotionEvent.ACTION_HOVER_EXIT: + // Hide the zoom controls + if (mapboxMap.getUiSettings().isZoomControlsEnabled()) { + zoomButtonsController.setVisible(false); + } + return true; + + default: + // We are not interested in this event + return false; + } } /** @@ -675,179 +588,6 @@ public class MapView extends FrameLayout { }); } - // - // LatLng / CenterCoordinate - // - - LatLng getLatLng() { - return nativeMapView.getLatLng(); - } - - // - // Pitch / Tilt - // - - double getTilt() { - return nativeMapView.getPitch(); - } - - void setTilt(Double pitch) { - mapboxMap.getMarkerViewManager().setTilt(pitch.floatValue()); - myLocationView.setTilt(pitch); - nativeMapView.setPitch(pitch, 0); - } - - // - // Center coordinate - // - - LatLng getCenterCoordinate() { - return nativeMapView.getLatLng(); - } - - void setCenterCoordinate(LatLng centerCoordinate) { - nativeMapView.setLatLng(centerCoordinate); - } - - // - // Direction - // - - double getDirection() { - if (destroyed) { - return 0; - } - - return convertNativeBearing(nativeMapView.getBearing()); - } - - void setDirection(@FloatRange(from = MapboxConstants.MINIMUM_DIRECTION, to = MapboxConstants.MAXIMUM_DIRECTION) double direction) { - if (destroyed) { - return; - } - setDirection(direction, false); - } - - void setDirection(@FloatRange(from = MapboxConstants.MINIMUM_DIRECTION, to = MapboxConstants.MAXIMUM_DIRECTION) double direction, boolean animated) { - if (destroyed) { - return; - } - long duration = animated ? MapboxConstants.ANIMATION_DURATION : 0; - mapboxMap.cancelTransitions(); - // Out of range directions are normalised in setBearing - nativeMapView.setBearing(-direction, duration); - } - - void resetNorth() { - if (destroyed) { - return; - } - myLocationView.setBearing(0); - mapboxMap.cancelTransitions(); - nativeMapView.resetNorth(); - } - - // - // Content padding - // - - int getContentPaddingLeft() { - return contentPaddingLeft; - } - - int getContentPaddingTop() { - return contentPaddingTop; - } - - int getContentPaddingRight() { - return contentPaddingRight; - } - - int getContentPaddingBottom() { - return contentPaddingBottom; - } - - // - // Zoom - // - - double getZoom() { - if (destroyed) { - return 0; - } - return nativeMapView.getZoom(); - } - - void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) { - if (destroyed) { - return; - } - nativeMapView.setMinZoom(minZoom); - } - - double getMinZoom() { - if (destroyed) { - return 0; - } - return nativeMapView.getMinZoom(); - } - - void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { - if (destroyed) { - return; - } - nativeMapView.setMaxZoom(maxZoom); - } - - double getMaxZoom() { - if (destroyed) { - return 0; - } - return nativeMapView.getMaxZoom(); - } - - // Zoom in or out - private void zoom(boolean zoomIn) { - zoom(zoomIn, -1.0f, -1.0f); - } - - private void zoom(boolean zoomIn, float x, float y) { - // Cancel any animation - 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); - } - } - - // - // Debug - // - - boolean isDebugActive() { - return !destroyed && nativeMapView.getDebug(); - } - - void setDebugActive(boolean debugActive) { - if (destroyed) { - return; - } - nativeMapView.setDebug(debugActive); - } - - void cycleDebugOptions() { - if (destroyed) { - return; - } - nativeMapView.cycleDebugOptions(); - } - - // - // Styling - // - /** * <p> * Loads a new map style from the specified URL. @@ -886,57 +626,9 @@ public class MapView extends FrameLayout { setAccessToken(MapboxAccountManager.getInstance().getAccessToken()); } - styleUrl = url; nativeMapView.setStyleUrl(url); - styleWasSet = true; - } - - /** - * <p> - * Loads a new map style from the specified bundled style. - * </p> - * <p> - * This method is asynchronous and will return immediately before the style finishes loading. - * If you wish to wait for the map to finish loading listen for the {@link MapView#DID_FINISH_LOADING_MAP} event. - * </p> - * If the style fails to load or an invalid style URL is set, the map view will become blank. - * An error message will be logged in the Android logcat and {@link MapView#DID_FAIL_LOADING_MAP} event will be sent. - * - * @param style The bundled style. Accepts one of the values from {@link Style}. - * @see Style - */ - @UiThread - public void setStyle(@Style.StyleUrl String style) { - setStyleUrl(style); - } - - /** - * <p> - * Returns the map style currently displayed in the map view. - * </p> - * If the default style is currently displayed, a URL will be returned instead of null. - * - * @return The URL of the map style. - */ - @UiThread - @NonNull - public String getStyleUrl() { - return styleUrl; } - // - // API Base URL - // - - @UiThread - void setApiBaseUrl(@NonNull String baseUrl) { - nativeMapView.setApiBaseUrl(baseUrl); - } - - // - // Access token - // - /** * <p> * DEPRECATED @see MapboxAccountManager#start(String) @@ -989,78 +681,6 @@ public class MapView extends FrameLayout { } // - // Projection - // - - /* - * Internal use only, use Projection#fromScreenLocation instead - */ - LatLng fromNativeScreenLocation(@NonNull PointF point) { - if (destroyed) { - return new LatLng(); - } - return nativeMapView.latLngForPixel(point); - } - - /* - * Internal use only, use Projection#toScreenLocation instead. - */ - PointF toNativeScreenLocation(@NonNull LatLng location) { - if (destroyed || location == null) { - return new PointF(); - } - return nativeMapView.pixelForLatLng(location); - } - - /** - * @return the ViewGroup containing the marker views - */ - public ViewGroup getMarkerViewContainer() { - return markerViewContainer; - } - - void setContentPadding(int left, int top, int right, int bottom) { - if (destroyed) { - return; - } - -// if (left == contentPaddingLeft && top == contentPaddingTop && right == contentPaddingRight && bottom == contentPaddingBottom) { -// return; -// } - - contentPaddingLeft = left; - contentPaddingTop = top; - contentPaddingRight = right; - contentPaddingBottom = bottom; - - int[] userLocationViewPadding = mapboxMap.getMyLocationViewSettings().getPadding(); - left += userLocationViewPadding[0]; - top += userLocationViewPadding[1]; - right += userLocationViewPadding[2]; - bottom += userLocationViewPadding[3]; - - nativeMapView.setContentPadding(top / screenDensity, left / screenDensity, bottom / screenDensity, right / screenDensity); - } - - public void invalidateContentPadding() { - setContentPadding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom); - - if (!mapboxMap.getTrackingSettings().isLocationTrackingDisabled()) { - setFocalPoint(new PointF(myLocationView.getCenterX(), myLocationView.getCenterY())); - } else { - setFocalPoint(null); - } - } - - double getMetersPerPixelAtLatitude(@FloatRange(from = -180, to = 180) double latitude) { - if (destroyed) { - return 0; - } - - return nativeMapView.getMetersPerPixelAtLatitude(latitude, getZoom()) / screenDensity; - } - - // // Rendering // @@ -1095,16 +715,8 @@ public class MapView extends FrameLayout { } if (!isInEditMode()) { - nativeMapView.resizeView((int) (width / screenDensity), (int) (height / screenDensity)); - } - } - - double getScale() { - if (destroyed) { - return 0; + nativeMapView.resizeView(width, height); } - - return nativeMapView.getScale(); } private class SurfaceCallback implements SurfaceHolder.Callback { @@ -1181,7 +793,8 @@ public class MapView extends FrameLayout { if (destroyed) { return; } - compassView.update(getDirection()); + // TODO move to transform. java + compassView.update(mapboxMap.getTransform().getBearing()); myLocationView.update(); mapboxMap.getMarkerViewManager().update(); @@ -1191,52 +804,6 @@ public class MapView extends FrameLayout { } } - void updateCameraPosition(@NonNull CameraPosition position) { - myLocationView.setCameraPosition(position); - mapboxMap.getMarkerViewManager().setTilt((float) position.tilt); - } - - double getBearing() { - if (destroyed) { - return 0; - } - - double direction = -nativeMapView.getBearing(); - - while (direction > 360) { - direction -= 360; - } - while (direction < 0) { - direction += 360; - } - - return direction; - } - - void setBearing(double bearing) { - if (destroyed) { - return; - } - myLocationView.setBearing(bearing); - nativeMapView.setBearing(bearing); - } - - void setBearing(double bearing, long duration) { - if (destroyed) { - return; - } - myLocationView.setBearing(bearing); - nativeMapView.setBearing(bearing, duration); - } - - void setBearing(double bearing, float focalX, float focalY) { - if (destroyed) { - return; - } - myLocationView.setBearing(bearing); - nativeMapView.setBearing(bearing, focalX, focalY); - } - // // View events // @@ -1267,898 +834,8 @@ public class MapView extends FrameLayout { return; } - // Required by ZoomButtonController (from Android SDK documentation) - if (visibility == View.VISIBLE) { - if (mapboxMap != null && mapboxMap.getUiSettings().isZoomControlsEnabled()) { - zoomButtonsController.setVisible(true); - } - } else { - if (mapboxMap != null && mapboxMap.getUiSettings().isZoomControlsEnabled()) { - zoomButtonsController.setVisible(false); - } - } - } - - // - // Touch events - // - - /** - * Helper method for tracking gesture events - * - * @param gestureId Type of Gesture See {@see MapboxEvent#GESTURE_SINGLETAP MapboxEvent#GESTURE_DOUBLETAP MapboxEvent#GESTURE_TWO_FINGER_SINGLETAP MapboxEvent#GESTURE_QUICK_ZOOM MapboxEvent#GESTURE_PAN_START MapboxEvent#GESTURE_PINCH_START MapboxEvent#GESTURE_ROTATION_START MapboxEvent#GESTURE_PITCH_START} - * @param xCoordinate Original x screen coordinate at start of gesture - * @param yCoordinate Original y screen cooridnate at start of gesture - */ - private void trackGestureEvent(@NonNull String gestureId, float xCoordinate, float yCoordinate) { - LatLng tapLatLng = projection.fromScreenLocation(new PointF(xCoordinate, yCoordinate)); - - // NaN and Infinite checks to prevent JSON errors at send to server time - if (Double.isNaN(tapLatLng.getLatitude()) || Double.isNaN(tapLatLng.getLongitude())) { - Timber.d("trackGestureEvent() has a NaN lat or lon. Returning."); - return; - } - - if (Double.isInfinite(tapLatLng.getLatitude()) || Double.isInfinite(tapLatLng.getLongitude())) { - Timber.d("trackGestureEvent() has an Infinite lat or lon. Returning."); - return; - } - - Hashtable<String, Object> evt = new Hashtable<>(); - evt.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_MAP_CLICK); - evt.put(MapboxEvent.ATTRIBUTE_CREATED, MapboxEventManager.generateCreateDate()); - evt.put(MapboxEvent.KEY_GESTURE_ID, gestureId); - evt.put(MapboxEvent.KEY_LATITUDE, tapLatLng.getLatitude()); - evt.put(MapboxEvent.KEY_LONGITUDE, tapLatLng.getLongitude()); - evt.put(MapboxEvent.KEY_ZOOM, mapboxMap.getCameraPosition().zoom); - - MapboxEventManager.getMapboxEventManager().pushEvent(evt); - } - - /** - * Helper method for tracking DragEnd gesture event - * See {@see MapboxEvent#TYPE_MAP_DRAGEND} - * - * @param xCoordinate Original x screen coordinate at end of drag - * @param yCoordinate Orginal y screen coordinate at end of drag - */ - private void trackGestureDragEndEvent(float xCoordinate, float yCoordinate) { - LatLng tapLatLng = projection.fromScreenLocation(new PointF(xCoordinate, yCoordinate)); - - // NaN and Infinite checks to prevent JSON errors at send to server time - if (Double.isNaN(tapLatLng.getLatitude()) || Double.isNaN(tapLatLng.getLongitude())) { - Timber.d("trackGestureDragEndEvent() has a NaN lat or lon. Returning."); - return; - } - - if (Double.isInfinite(tapLatLng.getLatitude()) || Double.isInfinite(tapLatLng.getLongitude())) { - Timber.d("trackGestureDragEndEvent() has an Infinite lat or lon. Returning."); - return; - } - - Hashtable<String, Object> evt = new Hashtable<>(); - evt.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_MAP_DRAGEND); - evt.put(MapboxEvent.ATTRIBUTE_CREATED, MapboxEventManager.generateCreateDate()); - evt.put(MapboxEvent.KEY_LATITUDE, tapLatLng.getLatitude()); - evt.put(MapboxEvent.KEY_LONGITUDE, tapLatLng.getLongitude()); - evt.put(MapboxEvent.KEY_ZOOM, mapboxMap.getCameraPosition().zoom); - - MapboxEventManager.getMapboxEventManager().pushEvent(evt); - } - - // Called when user touches the screen, all positions are absolute - @Override - public boolean onTouchEvent(@NonNull MotionEvent event) { - // Check and ignore non touch or left clicks - if (destroyed) { - return super.onTouchEvent(event); - } - - if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { - return false; - } - - // Check two finger gestures first - rotateGestureDetector.onTouchEvent(event); - scaleGestureDetector.onTouchEvent(event); - shoveGestureDetector.onTouchEvent(event); - - // Handle two finger tap - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - // First pointer down - nativeMapView.setGestureInProgress(true); - break; - - case MotionEvent.ACTION_POINTER_DOWN: - // Second pointer down - twoTap = event.getPointerCount() == 2 - && mapboxMap.getUiSettings().isZoomGesturesEnabled(); - if (twoTap) { - // Confirmed 2nd Finger Down - trackGestureEvent(MapboxEvent.GESTURE_TWO_FINGER_SINGLETAP, event.getX(), event.getY()); - } - break; - - case MotionEvent.ACTION_POINTER_UP: - // Second pointer up - break; - - case MotionEvent.ACTION_UP: - // First pointer up - long tapInterval = event.getEventTime() - event.getDownTime(); - boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); - boolean inProgress = rotateGestureDetector.isInProgress() - || scaleGestureDetector.isInProgress() - || shoveGestureDetector.isInProgress(); - - if (twoTap && isTap && !inProgress) { - if (focalPoint != null) { - zoom(false, focalPoint.x, focalPoint.y); - } else { - PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event); - zoom(false, focalPoint.x, focalPoint.y); - } - twoTap = false; - return true; - } - - // Scroll / Pan Has Stopped - if (scrollInProgress) { - trackGestureDragEndEvent(event.getX(), event.getY()); - scrollInProgress = false; - } - - twoTap = false; - nativeMapView.setGestureInProgress(false); - break; - - case MotionEvent.ACTION_CANCEL: - twoTap = false; - nativeMapView.setGestureInProgress(false); - break; - } - - boolean retVal = gestureDetector.onTouchEvent(event); - return retVal || super.onTouchEvent(event); - } - - // This class handles one finger gestures - private class GestureListener extends GestureDetector.SimpleOnGestureListener { - - // Must always return true otherwise all events are ignored - @Override - @SuppressLint("ResourceType") - public boolean onDown(MotionEvent event) { - // Show the zoom controls - if (mapboxMap.getUiSettings().isZoomControlsEnabled()) { - zoomButtonsController.setVisible(true); - } - return true; - } - - // Called for double taps - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - if (destroyed || !mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return false; - } - - switch (e.getAction()) { - case MotionEvent.ACTION_DOWN: - break; - case MotionEvent.ACTION_MOVE: - break; - case MotionEvent.ACTION_UP: - if (quickZoom) { - // insert here? - quickZoom = false; - break; - } - - // Single finger double tap - if (focalPoint != null) { - // User provided focal point - zoom(true, focalPoint.x, focalPoint.y); - } else { - // Zoom in on gesture - zoom(true, e.getX(), e.getY()); - } - break; - } - - trackGestureEvent(MapboxEvent.GESTURE_DOUBLETAP, e.getX(), e.getY()); - - return true; - } - - @Override - public boolean onSingleTapUp(MotionEvent motionEvent) { - if (destroyed) { - return false; - } - // Cancel any animation - mapboxMap.cancelTransitions(); - return true; - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent motionEvent) { - List<Marker> selectedMarkers = mapboxMap.getSelectedMarkers(); - - PointF tapPoint = new PointF(motionEvent.getX(), motionEvent.getY()); - float toleranceSides = 4 * screenDensity; - float toleranceTopBottom = 10 * screenDensity; - - RectF tapRect = new RectF(tapPoint.x - iconManager.getAverageIconWidth() / 2 - toleranceSides, - tapPoint.y - iconManager.getAverageIconHeight() / 2 - toleranceTopBottom, - tapPoint.x + iconManager.getAverageIconWidth() / 2 + toleranceSides, - tapPoint.y + iconManager.getAverageIconHeight() / 2 + toleranceTopBottom); - - List<Marker> nearbyMarkers = annotationManager.getMarkersInRect(tapRect); - long newSelectedMarkerId = -1; - - if (nearbyMarkers != null && nearbyMarkers.size() > 0) { - Collections.sort(nearbyMarkers); - for (Marker nearbyMarker : nearbyMarkers) { - boolean found = false; - for (Marker selectedMarker : selectedMarkers) { - if (selectedMarker.equals(nearbyMarker)) { - found = true; - } - } - if (!found) { - newSelectedMarkerId = nearbyMarker.getId(); - break; - } - } - } - - if (newSelectedMarkerId >= 0) { - List<Annotation> annotations = mapboxMap.getAnnotations(); - int count = annotations.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotations.get(i); - if (annotation instanceof Marker) { - if (annotation.getId() == newSelectedMarkerId) { - if (selectedMarkers.isEmpty() || !selectedMarkers.contains(annotation)) { - if (!(annotation instanceof MarkerView)) { - mapboxMap.selectMarker((Marker) annotation); - } else { - mapboxMap.getMarkerViewManager().onClickMarkerView((MarkerView) annotation); - } - } - break; - } - } - } - } else { - if (mapboxMap.getUiSettings().isDeselectMarkersOnTap()) { - // deselect any selected marker - mapboxMap.deselectMarkers(); - } - - // notify app of map click - MapboxMap.OnMapClickListener listener = mapboxMap.getOnMapClickListener(); - if (listener != null) { - LatLng point = projection.fromScreenLocation(tapPoint); - listener.onMapClick(point); - } - } - - trackGestureEvent(MapboxEvent.GESTURE_SINGLETAP, motionEvent.getX(), motionEvent.getY()); - return true; - } - - // Called for a long press - @Override - public void onLongPress(MotionEvent motionEvent) { - MapboxMap.OnMapLongClickListener listener = mapboxMap.getOnMapLongClickListener(); - if (listener != null && !quickZoom) { - LatLng point = projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY())); - listener.onMapLongClick(point); - } - } - - // Called for flings - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (destroyed || !mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(true, false); - - double decelerationRate = 1; - - // Cancel any animation - mapboxMap.cancelTransitions(); - - double offsetX = velocityX * decelerationRate / 4 / screenDensity; - double offsetY = velocityY * decelerationRate / 4 / screenDensity; - - nativeMapView.setGestureInProgress(true); - nativeMapView.moveBy(offsetX, offsetY, (long) (decelerationRate * 1000.0f)); - nativeMapView.setGestureInProgress(false); - - MapboxMap.OnFlingListener listener = mapboxMap.getOnFlingListener(); - if (listener != null) { - listener.onFling(); - } - - trackGestureEvent(MapboxEvent.GESTURE_PAN_START, e1.getX(), e1.getY()); - return true; - } - - // Called for drags - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!scrollInProgress) { - scrollInProgress = true; - } - if (destroyed || !mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - if (dragStarted) { - return false; - } - - requestDisallowInterceptTouchEvent(true); - - // reset tracking if needed - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(true, false); - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Scroll the map - nativeMapView.moveBy(-distanceX / screenDensity, -distanceY / screenDensity); - - MapboxMap.OnScrollListener listener = mapboxMap.getOnScrollListener(); - if (listener != null) { - listener.onScroll(); - } - return true; - } - } - - // This class handles two finger gestures and double-tap drag gestures - private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { - - long beginTime = 0; - float scaleFactor = 1.0f; - - // Called when two fingers first touch the screen - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - if (destroyed || !mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return false; - } - - beginTime = detector.getEventTime(); - trackGestureEvent(MapboxEvent.GESTURE_PINCH_START, detector.getFocusX(), detector.getFocusY()); - return true; - } - - // Called when fingers leave screen - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - beginTime = 0; - scaleFactor = 1.0f; - zoomStarted = false; - } - - // Called each time a finger moves - // Called for pinch zooms and quickzooms/quickscales - @Override - public boolean onScale(ScaleGestureDetector detector) { - UiSettings uiSettings = mapboxMap.getUiSettings(); - if (destroyed || !uiSettings.isZoomGesturesEnabled()) { - return super.onScale(detector); - } - - // If scale is large enough ignore a tap - scaleFactor *= detector.getScaleFactor(); - if ((scaleFactor > 1.05f) || (scaleFactor < 0.95f)) { - zoomStarted = true; - } - - // Ignore short touches in case it is a tap - // Also ignore small scales - long time = detector.getEventTime(); - long interval = time - beginTime; - if (!zoomStarted && (interval <= ViewConfiguration.getTapTimeout())) { - return false; - } - - if (!zoomStarted) { - return false; - } - - if (dragStarted) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Gesture is a quickzoom if there aren't two fingers - quickZoom = !twoTap; - - // 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. - - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(!quickZoom, false); - // Scale the map - if (focalPoint != null) { - // arround user provided focal point - nativeMapView.scaleBy(detector.getScaleFactor(), focalPoint.x / screenDensity, focalPoint.y / screenDensity); - } else if (quickZoom) { - // around center map - nativeMapView.scaleBy(detector.getScaleFactor(), (getWidth() / 2) / screenDensity, (getHeight() / 2) / screenDensity); - } else { - // around gesture - nativeMapView.scaleBy(detector.getScaleFactor(), detector.getFocusX() / screenDensity, detector.getFocusY() / screenDensity); - } - - return true; - } - } - - // This class handles two finger rotate gestures - private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { - - long beginTime = 0; - float totalAngle = 0.0f; - boolean started = false; - - // Called when two fingers first touch the screen - @Override - public boolean onRotateBegin(RotateGestureDetector detector) { - if (destroyed || !mapboxMap.getTrackingSettings().isRotateGestureCurrentlyEnabled()) { - return false; - } - - beginTime = detector.getEventTime(); - trackGestureEvent(MapboxEvent.GESTURE_ROTATION_START, detector.getFocusX(), detector.getFocusY()); - return true; - } - - // Called when the fingers leave the screen - @Override - public void onRotateEnd(RotateGestureDetector detector) { - beginTime = 0; - totalAngle = 0.0f; - started = false; - } - - // Called each time one of the two fingers moves - // Called for rotation - @Override - public boolean onRotate(RotateGestureDetector detector) { - if (destroyed || !mapboxMap.getTrackingSettings().isRotateGestureCurrentlyEnabled() || dragStarted) { - return false; - } - - // If rotate is large enough ignore a tap - // Also is zoom already started, don't rotate - totalAngle += detector.getRotationDegreesDelta(); - if (!zoomStarted && ((totalAngle > 20.0f) || (totalAngle < -20.0f))) { - started = true; - } - - // 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())) { - return false; - } - - if (!started) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // rotation constitutes translation of anything except the center of - // rotation, so cancel both location and bearing tracking if required - - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(true, true); - - // Get rotate value - double bearing = nativeMapView.getBearing(); - bearing += detector.getRotationDegreesDelta(); - - // Rotate the map - if (focalPoint != null) { - // User provided focal point - setBearing(bearing, focalPoint.x / screenDensity, focalPoint.y / screenDensity); - } else { - // around gesture - setBearing(bearing, detector.getFocusX() / screenDensity, detector.getFocusY() / screenDensity); - } - return true; - } - } - - // This class handles a vertical two-finger shove. (If you place two fingers on screen with - // less than a 20 degree angle between them, this will detect movement on the Y-axis.) - private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener { - - long beginTime = 0; - float totalDelta = 0.0f; - boolean started = false; - - @Override - public boolean onShoveBegin(ShoveGestureDetector detector) { - if (!mapboxMap.getUiSettings().isTiltGesturesEnabled()) { - return false; - } - - beginTime = detector.getEventTime(); - trackGestureEvent(MapboxEvent.GESTURE_PITCH_START, detector.getFocusX(), detector.getFocusY()); - return true; - } - - @Override - public void onShoveEnd(ShoveGestureDetector detector) { - beginTime = 0; - totalDelta = 0.0f; - started = false; - dragStarted = false; - } - - @Override - public boolean onShove(ShoveGestureDetector detector) { - if (destroyed || !mapboxMap.getUiSettings().isTiltGesturesEnabled()) { - return false; - } - - // If tilt is large enough ignore a tap - // Also if zoom already started, don't tilt - totalDelta += detector.getShovePixelsDelta(); - if (!zoomStarted && ((totalDelta > 10.0f) || (totalDelta < -10.0f))) { - started = true; - } - - // Ignore short touches in case it is a tap - // Also ignore small tilt - long time = detector.getEventTime(); - long interval = time - beginTime; - if (!started && (interval <= ViewConfiguration.getTapTimeout())) { - return false; - } - - if (!started) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Get tilt value (scale and clamp) - double pitch = getTilt(); - pitch -= 0.1 * detector.getShovePixelsDelta(); - pitch = Math.max(MapboxConstants.MINIMUM_TILT, Math.min(MapboxConstants.MAXIMUM_TILT, pitch)); - - // Tilt the map - mapboxMap.setTilt(pitch); - - dragStarted = true; - - return true; - } - } - - // This class handles input events from the zoom control buttons - // Zoom controls allow single touch only devices to zoom in and out - private class OnZoomListener implements ZoomButtonsController.OnZoomListener { - - // Not used - @Override - public void onVisibilityChanged(boolean visible) { - // Ignore - } - - // Called when user pushes a zoom button - @Override - public void onZoom(boolean zoomIn) { - if (!mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return; - } - zoom(zoomIn); - } - } - - // - // Input events - // - - // Called when the user presses a key, also called for repeating keys held - // down - @Override - public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { - if (destroyed) { - return super.onKeyDown(keyCode, event); - } - - // If the user has held the scroll key down for a while then accelerate - // the scroll speed - double scrollDist = event.getRepeatCount() >= 5 ? 50.0 : 10.0; - - // Check which key was pressed via hardware/real key code - switch (keyCode) { - // Tell the system to track these keys for long presses on - // onKeyLongPress is fired - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - event.startTracking(); - return true; - - case KeyEvent.KEYCODE_DPAD_LEFT: - if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Move left - nativeMapView.moveBy(scrollDist / screenDensity, 0.0 / screenDensity); - return true; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Move right - nativeMapView.moveBy(-scrollDist / screenDensity, 0.0 / screenDensity); - return true; - - case KeyEvent.KEYCODE_DPAD_UP: - if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Move up - nativeMapView.moveBy(0.0 / screenDensity, scrollDist / screenDensity); - return true; - - case KeyEvent.KEYCODE_DPAD_DOWN: - if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Move down - nativeMapView.moveBy(0.0 / screenDensity, -scrollDist / screenDensity); - return true; - - default: - // We are not interested in this key - return super.onKeyUp(keyCode, event); - } - } - - // Called when the user long presses a key that is being tracked - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - // Check which key was pressed via hardware/real key code - switch (keyCode) { - // Tell the system to track these keys for long presses on - // onKeyLongPress is fired - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - if (!mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return false; - } - - // Zoom out - zoom(false); - return true; - - default: - // We are not interested in this key - return super.onKeyUp(keyCode, event); - } - } - - // Called when the user releases a key - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - // Check if the key action was canceled (used for virtual keyboards) - if (event.isCanceled()) { - return super.onKeyUp(keyCode, event); - } - - // Check which key was pressed via hardware/real key code - // Note if keyboard does not have physical key (ie primary non-shifted - // key) then it will not appear here - // Must use the key character map as physical to character is not - // fixed/guaranteed - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - if (!mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return false; - } - - // Zoom in - zoom(true); - return true; - } - - // We are not interested in this key - return super.onKeyUp(keyCode, event); - } - - // Called for trackball events, all motions are relative in device specific - // units - @Override - public boolean onTrackballEvent(MotionEvent event) { - if (destroyed) { - return false; - } - // Choose the action - switch (event.getActionMasked()) { - // The trackball was rotated - case MotionEvent.ACTION_MOVE: - if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Scroll the map - nativeMapView.moveBy(-10.0 * event.getX() / screenDensity, -10.0 * event.getY() / screenDensity); - return true; - - // Trackball was pushed in so start tracking and tell system we are - // interested - // We will then get the up action - case MotionEvent.ACTION_DOWN: - // Set up a delayed callback to check if trackball is still - // After waiting the system long press time out - if (currentTrackballLongPressTimeOut != null) { - currentTrackballLongPressTimeOut.cancel(); - currentTrackballLongPressTimeOut = null; - } - currentTrackballLongPressTimeOut = new TrackballLongPressTimeOut(); - postDelayed(currentTrackballLongPressTimeOut, - ViewConfiguration.getLongPressTimeout()); - return true; - - // Trackball was released - case MotionEvent.ACTION_UP: - if (!mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return false; - } - - // Only handle if we have not already long pressed - if (currentTrackballLongPressTimeOut != null) { - // Zoom in - zoom(true); - } - return true; - - // Trackball was cancelled - case MotionEvent.ACTION_CANCEL: - if (currentTrackballLongPressTimeOut != null) { - currentTrackballLongPressTimeOut.cancel(); - currentTrackballLongPressTimeOut = null; - } - return true; - - default: - // We are not interested in this event - return super.onTrackballEvent(event); - } - } - - // This class implements the trackball long press time out callback - private class TrackballLongPressTimeOut implements Runnable { - - // Track if we have been cancelled - private boolean cancelled; - - TrackballLongPressTimeOut() { - cancelled = false; - } - - // Cancel the timeout - public void cancel() { - cancelled = true; - } - - // Called when long press time out expires - @Override - public void run() { - // Check if the trackball is still pressed - if (!cancelled) { - // Zoom out - zoom(false); - - // Ensure the up action is not run - currentTrackballLongPressTimeOut = null; - } - } - } - - // Called for events that don't fit the other handlers - // such as mouse scroll events, mouse moves, joystick, trackpad - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if (destroyed) { - return false; - } - // Mouse events - //if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18 - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == InputDevice.SOURCE_CLASS_POINTER) { - // Choose the action - switch (event.getActionMasked()) { - // Mouse scrolls - case MotionEvent.ACTION_SCROLL: - if (!mapboxMap.getUiSettings().isZoomGesturesEnabled()) { - return false; - } - - // Cancel any animation - mapboxMap.cancelTransitions(); - - // Get the vertical scroll amount, one click = 1 - float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL); - - // Scale the map by the appropriate power of two factor - nativeMapView.scaleBy(Math.pow(2.0, scrollDist), event.getX() / screenDensity, event.getY() / screenDensity); - - return true; - - default: - // We are not interested in this event - return super.onGenericMotionEvent(event); - } - } - - // We are not interested in this event - return super.onGenericMotionEvent(event); - } - - // Called when the mouse pointer enters or exits the view - // or when it fades in or out due to movement - @Override - public boolean onHoverEvent(@NonNull MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_HOVER_ENTER: - case MotionEvent.ACTION_HOVER_MOVE: - // Show the zoom controls - if (mapboxMap.getUiSettings().isZoomControlsEnabled()) { - zoomButtonsController.setVisible(true); - } - return true; - - case MotionEvent.ACTION_HOVER_EXIT: - // Hide the zoom controls - if (mapboxMap.getUiSettings().isZoomControlsEnabled()) { - zoomButtonsController.setVisible(false); - } - return true; - - default: - // We are not interested in this event - return super.onHoverEvent(event); + if (mapboxMap != null && mapboxMap.getUiSettings().isZoomControlsEnabled()) { + zoomButtonsController.setVisible(visibility == View.VISIBLE); } } @@ -2230,133 +907,6 @@ public class MapView extends FrameLayout { nativeMapView.onMapChangedEventDispatch(mapChange); } - // - // User location - // - - void setMyLocationEnabled(boolean enabled) { - myLocationView.setEnabled(enabled); - } - - Location getMyLocation() { - return myLocationView.getLocation(); - } - - void setOnMyLocationChangeListener(@Nullable final MapboxMap.OnMyLocationChangeListener listener) { - if (listener != null) { - myLocationListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - if (listener != null) { - listener.onMyLocationChange(location); - } - } - }; - LocationServices.getLocationServices(getContext()).addLocationListener(myLocationListener); - } else { - LocationServices.getLocationServices(getContext()).removeLocationListener(myLocationListener); - myLocationListener = null; - } - } - - void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) { - if (myLocationTrackingMode != MyLocationTracking.TRACKING_NONE && !mapboxMap.isMyLocationEnabled()) { - mapboxMap.setMyLocationEnabled(true); - } - myLocationView.setMyLocationTrackingMode(myLocationTrackingMode); - - if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) { - setFocalPoint(new PointF(myLocationView.getCenterX(), myLocationView.getCenterY())); - } else { - setFocalPoint(null); - } - - MapboxMap.OnMyLocationTrackingModeChangeListener listener = mapboxMap.getOnMyLocationTrackingModeChangeListener(); - if (listener != null) { - listener.onMyLocationTrackingModeChange(myLocationTrackingMode); - } - } - - void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) { - if (myBearingTrackingMode != MyBearingTracking.NONE && !mapboxMap.isMyLocationEnabled()) { - mapboxMap.setMyLocationEnabled(true); - } - myLocationView.setMyBearingTrackingMode(myBearingTrackingMode); - MapboxMap.OnMyBearingTrackingModeChangeListener listener = mapboxMap.getOnMyBearingTrackingModeChangeListener(); - if (listener != null) { - listener.onMyBearingTrackingModeChange(myBearingTrackingMode); - } - } - - boolean isPermissionsAccepted() { - return (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) || - ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; - } - - // - // Compass - // - - void setCompassEnabled(boolean compassEnabled) { - compassView.setEnabled(compassEnabled); - } - - void setCompassGravity(int gravity) { - setWidgetGravity(compassView, gravity); - } - - void setCompassMargins(int left, int top, int right, int bottom) { - setWidgetMargins(compassView, left, top, right, bottom); - } - - void setCompassFadeFacingNorth(boolean compassFadeFacingNorth) { - compassView.fadeCompassViewFacingNorth(compassFadeFacingNorth); - } - - // - // Logo - // - - void setLogoGravity(int gravity) { - setWidgetGravity(logoView, gravity); - } - - void setLogoMargins(int left, int top, int right, int bottom) { - setWidgetMargins(logoView, left, top, right, bottom); - } - - void setLogoEnabled(boolean visible) { - logoView.setVisibility(visible ? View.VISIBLE : View.GONE); - } - - // - // Attribution - // - - void setAttributionGravity(int gravity) { - setWidgetGravity(attributionsView, gravity); - } - - void setAttributionMargins(int left, int top, int right, int bottom) { - setWidgetMargins(attributionsView, left, top, right, bottom); - } - - void setAttributionEnabled(int visibility) { - attributionsView.setVisibility(visibility); - } - - void setAtttibutionTintColor(int tintColor) { - // Check that the tint color being passed in isn't transparent. - if (Color.alpha(tintColor) == 0) { - ColorUtils.setTintList(attributionsView, ContextCompat.getColor(getContext(), R.color.mapbox_blue)); - } else { - ColorUtils.setTintList(attributionsView, tintColor); - } - } - - int getAttributionTintColor() { - return mapboxMap.getUiSettings().getAttributionTintColor(); - } /** * Sets a callback object which will be triggered when the {@link MapboxMap} instance is ready to be used. @@ -2382,14 +932,6 @@ public class MapView extends FrameLayout { this.mapboxMap = mapboxMap; } - MyLocationView getUserLocationView() { - return myLocationView; - } - - NativeMapView getNativeMapView() { - return nativeMapView; - } - // // Snapshot API // @@ -2437,26 +979,6 @@ public class MapView extends FrameLayout { } } - // - // View utility methods - // - - private void setWidgetGravity(@NonNull final View view, int gravity) { - LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); - layoutParams.gravity = gravity; - view.setLayoutParams(layoutParams); - } - - private void setWidgetMargins(@NonNull final View view, int left, int top, int right, int bottom) { - LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); - left += contentPaddingLeft; - top += contentPaddingTop; - right += contentPaddingRight; - bottom += contentPaddingBottom; - layoutParams.setMargins(left, top, right, bottom); - view.setLayoutParams(layoutParams); - } - private static class AttributionOnClickListener implements View.OnClickListener, DialogInterface.OnClickListener { private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; @@ -2511,16 +1033,16 @@ public class MapView extends FrameLayout { } }); - AlertDialog telemDialog = builder.show(); - telemDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mapView.getAttributionTintColor()); - telemDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mapView.getAttributionTintColor()); - telemDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(mapView.getAttributionTintColor()); + builder.show(); return; } String url = context.getResources().getStringArray(R.array.mapbox_attribution_links)[which]; if (which == ATTRIBUTION_INDEX_IMPROVE_THIS_MAP) { - LatLng latLng = mapView.getMapboxMap().getCameraPosition().target; - url = String.format(url, latLng.getLongitude(), latLng.getLatitude(), (int) mapView.getZoom()); + CameraPosition cameraPosition = mapView.getMapboxMap().getCameraPosition(); + if (cameraPosition != null) { + url = String.format(url, cameraPosition.target.getLongitude(), + cameraPosition.target.getLatitude(), (int) cameraPosition.zoom); + } } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); @@ -2767,4 +1289,66 @@ public class MapView extends FrameLayout { void onMapChanged(@MapChange int change); } + // This class handles input events from the zoom control buttons + // Zoom controls allow single touch only devices to zoom in and out + private class OnZoomListener implements ZoomButtonsController.OnZoomListener { + + private UiSettings uiSettings; + private Transform transform; + + OnZoomListener(MapboxMap mapboxMap) { + this.uiSettings = mapboxMap.getUiSettings(); + this.transform = mapboxMap.getTransform(); + } + + // Not used + @Override + public void onVisibilityChanged(boolean visible) { + // Ignore + } + + // Called when user pushes a zoom button + @Override + public void onZoom(boolean zoomIn) { + if (!uiSettings.isZoomGesturesEnabled()) { + return; + } + transform.zoom(zoomIn); + } + } + + private class FocalPointInvalidator implements FocalPointChangeListener { + + @Override + public void onFocalPointChanged(PointF pointF) { + mapGestureDetector.setFocalPoint(pointF); + } + } + + private class RegisterTouchListener implements MapboxMap.OnRegisterTouchListener { + + @Override + public void onRegisterMapClickListener(MapboxMap.OnMapClickListener listener) { + mapGestureDetector.setOnMapClickListener(listener); + } + + @Override + public void onRegisterMapLongClickListener(MapboxMap.OnMapLongClickListener listener) { + mapGestureDetector.setOnMapLongClickListener(listener); + } + + @Override + public void onRegisterScrollListener(MapboxMap.OnScrollListener listener) { + mapGestureDetector.setOnScrollListener(listener); + } + + @Override + public void onRegisterFlingListener(MapboxMap.OnFlingListener listener) { + mapGestureDetector.setOnFlingListener(listener); + } + } + + NativeMapView getNativeMapView() { + return nativeMapView; + } } 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 fa856a7aa7..2923fa2e52 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 @@ -1,20 +1,27 @@ package com.mapbox.mapboxsdk.maps; import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; import android.location.Location; +import android.os.Handler; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.util.Pools; + import timber.log.Timber; + +import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; +import android.widget.ZoomButtonsController; import com.mapbox.mapboxsdk.MapboxAccountManager; +import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; @@ -40,6 +47,7 @@ import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.NoSuchLayerException; import com.mapbox.mapboxsdk.style.sources.NoSuchSourceException; import com.mapbox.mapboxsdk.style.sources.Source; +import com.mapbox.mapboxsdk.utils.ColorUtils; import com.mapbox.services.commons.geojson.Feature; import java.lang.reflect.ParameterizedType; @@ -54,46 +62,134 @@ import java.util.List; * Note: Similar to a View object, a MapboxMap should only be read and modified from the main thread. * </p> */ -public class MapboxMap { - - private MapView mapView; - private UiSettings uiSettings; - private TrackingSettings trackingSettings; - private MyLocationViewSettings myLocationViewSettings; - private Projection projection; - private Transform transform; - private boolean myLocationEnabled; +public final class MapboxMap { - private MapboxMap.OnMapClickListener onMapClickListener; - private MapboxMap.OnMapLongClickListener onMapLongClickListener; + private final NativeMapView nativeMapView; + private final UiSettings uiSettings; + private final TrackingSettings trackingSettings; + private final Projection projection; + private final Transform transform; + private final AnnotationManager annotationManager; + private final MyLocationViewSettings myLocationViewSettings; + private final OnRegisterTouchListener onRegisterTouchListener; - - private MapboxMap.OnFlingListener onFlingListener; - private MapboxMap.OnScrollListener onScrollListener; - private MapboxMap.OnMyLocationTrackingModeChangeListener onMyLocationTrackingModeChangeListener; - private MapboxMap.OnMyBearingTrackingModeChangeListener onMyBearingTrackingModeChangeListener; private MapboxMap.OnFpsChangedListener onFpsChangedListener; - private AnnotationManager annotationManager; - private InfoWindowManager infoWindowManager; + private boolean myLocationEnabled; private double maxZoomLevel = -1; private double minZoomLevel = -1; - MapboxMap(@NonNull MapView mapView, IconManager iconManager) { - this.mapView = mapView; - 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()); + MapboxMap(NativeMapView map, Transform transform, UiSettings ui, TrackingSettings tracking, MyLocationViewSettings myLocationView, + Projection projection, OnRegisterTouchListener listener, AnnotationManager annotations) { + this.nativeMapView = map; + this.uiSettings = ui; + this.trackingSettings = tracking; + this.projection = projection; + this.myLocationViewSettings = myLocationView; + this.annotationManager = annotations.bind(this); + this.transform = transform; + this.onRegisterTouchListener = listener; + } + + void initialise(Context context, MapboxMapOptions options) { + // TODO migrate code from this method to the classes they impact themselves + setDebugActive(options.getDebugActive()); + + CameraPosition position = options.getCamera(); + if (position != null && !position.equals(CameraPosition.DEFAULT)) { + transform.moveCamera(this, CameraUpdateFactory.newCameraPosition(position), null); + myLocationViewSettings.setTilt(position.tilt); + } + + // api base url + String apiBaseUrl = options.getApiBaseUrl(); + if (!TextUtils.isEmpty(apiBaseUrl)) { + nativeMapView.setApiBaseUrl(apiBaseUrl); + } + + // access token + String accessToken = options.getAccessToken(); + if (!TextUtils.isEmpty(accessToken)) { + nativeMapView.setAccessToken(accessToken); + }else{ + nativeMapView.setAccessToken(MapboxAccountManager.getInstance().getAccessToken()); + } + + // style url + String style = options.getStyle(); + if (!TextUtils.isEmpty(style)) { + nativeMapView.setStyleUrl(style); + } + + // MyLocationView + myLocationViewSettings.setForegroundDrawable( + options.getMyLocationForegroundDrawable(), options.getMyLocationForegroundBearingDrawable()); + myLocationViewSettings.setForegroundTintColor(options.getMyLocationForegroundTintColor()); + myLocationViewSettings.setBackgroundDrawable( + options.getMyLocationBackgroundDrawable(), options.getMyLocationBackgroundPadding()); + myLocationViewSettings.setBackgroundTintColor(options.getMyLocationBackgroundTintColor()); + myLocationViewSettings.setAccuracyAlpha(options.getMyLocationAccuracyAlpha()); + myLocationViewSettings.setAccuracyTintColor(options.getMyLocationAccuracyTintColor()); + setMyLocationEnabled(options.getLocationEnabled()); + + // Enable gestures + uiSettings.setZoomGesturesEnabled(options.getZoomGesturesEnabled()); + uiSettings.setZoomGestureChangeAllowed(options.getZoomGesturesEnabled()); + uiSettings.setScrollGesturesEnabled(options.getScrollGesturesEnabled()); + uiSettings.setScrollGestureChangeAllowed(options.getScrollGesturesEnabled()); + uiSettings.setRotateGesturesEnabled(options.getRotateGesturesEnabled()); + uiSettings.setRotateGestureChangeAllowed(options.getRotateGesturesEnabled()); + uiSettings.setTiltGesturesEnabled(options.getTiltGesturesEnabled()); + uiSettings.setTiltGestureChangeAllowed(options.getTiltGesturesEnabled()); + + // Ui Controls + uiSettings.setZoomControlsEnabled(options.getZoomControlsEnabled()); + + // Zoom + setMaxZoom(options.getMaxZoom()); + setMinZoom(options.getMinZoom()); + + Resources resources = context.getResources(); + + // Compass + uiSettings.setCompassEnabled(options.getCompassEnabled()); + uiSettings.setCompassGravity(options.getCompassGravity()); + int[] compassMargins = options.getCompassMargins(); + if (compassMargins != null) { + uiSettings.setCompassMargins(compassMargins[0], compassMargins[1], compassMargins[2], compassMargins[3]); + } else { + int tenDp = (int) resources.getDimension(R.dimen.mapbox_ten_dp); + uiSettings.setCompassMargins(tenDp, tenDp, tenDp, tenDp); } + uiSettings.setCompassFadeFacingNorth(options.getCompassFadeFacingNorth()); + + // Logo + uiSettings.setLogoEnabled(options.getLogoEnabled()); + uiSettings.setLogoGravity(options.getLogoGravity()); + int[] logoMargins = options.getLogoMargins(); + if (logoMargins != null) { + uiSettings.setLogoMargins(logoMargins[0], logoMargins[1], logoMargins[2], logoMargins[3]); + } else { + int sixteenDp = (int) resources.getDimension(R.dimen.mapbox_sixteen_dp); + uiSettings.setLogoMargins(sixteenDp, sixteenDp, sixteenDp, sixteenDp); + } + + // Attribution + uiSettings.setAttributionEnabled(options.getAttributionEnabled()); + uiSettings.setAttributionGravity(options.getAttributionGravity()); + int[] attributionMargins = options.getAttributionMargins(); + if (attributionMargins != null) { + uiSettings.setAttributionMargins(attributionMargins[0], attributionMargins[1], attributionMargins[2], attributionMargins[3]); + } else { + int sevenDp = (int) resources.getDimension(R.dimen.mapbox_seven_dp); + int seventySixDp = (int) resources.getDimension(R.dimen.mapbox_seventy_six_dp); + uiSettings.setAttributionMargins(seventySixDp, sevenDp, sevenDp, sevenDp); + } + + int attributionTintColor = options.getAttributionTintColor(); + uiSettings.setAttributionTintColor(attributionTintColor != -1 + ? attributionTintColor : ColorUtils.getPrimaryColor(context)); } // Style @@ -101,7 +197,7 @@ public class MapboxMap { @Nullable @UiThread public Layer getLayer(@NonNull String layerId) { - return getMapView().getNativeMapView().getLayer(layerId); + return nativeMapView.getLayer(layerId); } /** @@ -116,7 +212,7 @@ public class MapboxMap { public <T extends Layer> T getLayerAs(@NonNull String layerId) { try { //noinspection unchecked - return (T) getMapView().getNativeMapView().getLayer(layerId); + return (T) nativeMapView.getLayer(layerId); } catch (ClassCastException e) { Timber.e(String.format("Layer: %s is a different type: %s", layerId, e.getMessage())); return null; @@ -141,7 +237,7 @@ public class MapboxMap { */ @UiThread public void addLayer(@NonNull Layer layer, String before) { - getMapView().getNativeMapView().addLayer(layer, before); + nativeMapView.addLayer(layer, before); } /** @@ -152,7 +248,7 @@ public class MapboxMap { */ @UiThread public void removeLayer(@NonNull String layerId) throws NoSuchLayerException { - getMapView().getNativeMapView().removeLayer(layerId); + nativeMapView.removeLayer(layerId); } /** @@ -163,13 +259,13 @@ public class MapboxMap { */ @UiThread public void removeLayer(@NonNull Layer layer) throws NoSuchLayerException { - getMapView().getNativeMapView().removeLayer(layer); + nativeMapView.removeLayer(layer); } @Nullable @UiThread public Source getSource(@NonNull String sourceId) { - return getMapView().getNativeMapView().getSource(sourceId); + return nativeMapView.getSource(sourceId); } /** @@ -184,7 +280,7 @@ public class MapboxMap { public <T extends Source> T getSourceAs(@NonNull String sourceId) { try { //noinspection unchecked - return (T) getMapView().getNativeMapView().getSource(sourceId); + return (T) nativeMapView.getSource(sourceId); } catch (ClassCastException e) { Timber.e(String.format("Source: %s is a different type: %s", sourceId, e.getMessage())); return null; @@ -198,7 +294,7 @@ public class MapboxMap { */ @UiThread public void addSource(@NonNull Source source) { - getMapView().getNativeMapView().addSource(source); + nativeMapView.addSource(source); } /** @@ -209,7 +305,7 @@ public class MapboxMap { */ @UiThread public void removeSource(@NonNull String sourceId) throws NoSuchSourceException { - getMapView().getNativeMapView().removeSource(sourceId); + nativeMapView.removeSource(sourceId); } /** @@ -220,7 +316,7 @@ public class MapboxMap { */ @UiThread public void removeSource(@NonNull Source source) throws NoSuchSourceException { - getMapView().getNativeMapView().removeSource(source); + nativeMapView.removeSource(source); } /** @@ -231,7 +327,7 @@ public class MapboxMap { */ @UiThread public void addImage(@NonNull String name, @NonNull Bitmap image) { - getMapView().getNativeMapView().addImage(name, image); + nativeMapView.addImage(name, image); } /** @@ -241,7 +337,7 @@ public class MapboxMap { */ @UiThread public void removeImage(String name) { - getMapView().getNativeMapView().removeImage(name); + nativeMapView.removeImage(name); } // @@ -263,7 +359,7 @@ public class MapboxMap { return; } minZoomLevel = minZoom; - mapView.setMinZoom(minZoom); + nativeMapView.setMinZoom(minZoom); } /** @@ -276,7 +372,7 @@ public class MapboxMap { @UiThread public double getMinZoom() { if (minZoomLevel == -1) { - return minZoomLevel = mapView.getMinZoom(); + return minZoomLevel = nativeMapView.getMinZoom(); } return minZoomLevel; } @@ -300,7 +396,7 @@ public class MapboxMap { return; } maxZoomLevel = maxZoom; - mapView.setMaxZoom(maxZoom); + nativeMapView.setMaxZoom(maxZoom); } /** @@ -313,7 +409,7 @@ public class MapboxMap { @UiThread public double getMaxZoom() { if (maxZoomLevel == -1) { - return maxZoomLevel = mapView.getMaxZoom(); + return maxZoomLevel = nativeMapView.getMaxZoom(); } return maxZoomLevel; } @@ -354,9 +450,6 @@ public class MapboxMap { * @return the MyLocationViewSettings associated with this map */ public MyLocationViewSettings getMyLocationViewSettings() { - if (myLocationViewSettings == null) { - myLocationViewSettings = new MyLocationViewSettings(mapView, mapView.getUserLocationView()); - } return myLocationViewSettings; } @@ -420,6 +513,9 @@ public class MapboxMap { @UiThread public final void moveCamera(CameraUpdate update) { moveCamera(update, null); + // MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo` + // invalidate camera position to provide OnCameraChange event. + invalidateCameraPosition(); } /** @@ -432,10 +528,10 @@ public class MapboxMap { */ @UiThread public final void moveCamera(final CameraUpdate update, final MapboxMap.CancelableCallback callback) { - mapView.post(new Runnable() { + new Handler().post(new Runnable() { @Override public void run() { - transform.moveCamera(update, callback); + transform.moveCamera(MapboxMap.this, update, callback); } }); } @@ -559,10 +655,10 @@ public class MapboxMap { */ @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() { + new Handler().post(new Runnable() { @Override public void run() { - transform.easeCamera(update, durationMs, easingInterpolator, resetTrackingMode, callback); + transform.easeCamera(MapboxMap.this, update, durationMs, easingInterpolator, resetTrackingMode, callback); } }); } @@ -634,10 +730,10 @@ public class MapboxMap { */ @UiThread public final void animateCamera(final CameraUpdate update, final int durationMs, final MapboxMap.CancelableCallback callback) { - mapView.post(new Runnable() { + new Handler().post(new Runnable() { @Override public void run() { - transform.animateCamera(update, durationMs, callback); + transform.animateCamera(MapboxMap.this, update, durationMs, callback); } }); } @@ -648,7 +744,7 @@ public class MapboxMap { void invalidateCameraPosition() { CameraPosition cameraPosition = transform.invalidateCameraPosition(); if (cameraPosition != null) { - mapView.updateCameraPosition(cameraPosition); + transform.updateCameraPosition(cameraPosition); } } @@ -674,7 +770,7 @@ public class MapboxMap { */ @UiThread public boolean isDebugActive() { - return mapView.isDebugActive(); + return nativeMapView.getDebug(); } /** @@ -687,21 +783,21 @@ public class MapboxMap { */ @UiThread public void setDebugActive(boolean debugActive) { - mapView.setDebugActive(debugActive); + nativeMapView.setDebug(debugActive); } /** * <p> * Cycles through the map debug options. * </p> - * The value of {@link MapView#isDebugActive()} reflects whether there are + * The value of isDebugActive reflects whether there are * any map debug options enabled or disabled. * - * @see MapView#isDebugActive() + * @see #isDebugActive() */ @UiThread public void cycleDebugOptions() { - mapView.cycleDebugOptions(); + nativeMapView.cycleDebugOptions(); } // @@ -739,7 +835,7 @@ public class MapboxMap { */ @UiThread public void setStyleUrl(@NonNull String url) { - mapView.setStyleUrl(url); + nativeMapView.setStyleUrl(url); } /** @@ -775,7 +871,7 @@ public class MapboxMap { @UiThread @NonNull public String getStyleUrl() { - return mapView.getStyleUrl(); + return nativeMapView.getStyleUrl(); } // @@ -797,7 +893,7 @@ public class MapboxMap { @Deprecated @UiThread public void setAccessToken(@NonNull String accessToken) { - mapView.setAccessToken(accessToken); + nativeMapView.setAccessToken(accessToken); } /** @@ -815,17 +911,13 @@ public class MapboxMap { @UiThread @Nullable public String getAccessToken() { - return mapView.getAccessToken(); + return nativeMapView.getAccessToken(); } // // Annotations // - void setTilt(double tilt) { - mapView.setTilt(tilt); - } - /** * <p> * Adds a marker to this map. @@ -1152,7 +1244,7 @@ public class MapboxMap { Timber.w("marker was null, so just returning"); return; } - annotationManager.selectMarker(marker, this); + annotationManager.selectMarker(marker); } /** @@ -1189,7 +1281,7 @@ public class MapboxMap { * @return the associated MarkerViewManager */ public MarkerViewManager getMarkerViewManager() { - return annotationManager.getMarkerViewManager(this); + return annotationManager.getMarkerViewManager(); } // @@ -1208,7 +1300,7 @@ public class MapboxMap { */ @UiThread public void setInfoWindowAdapter(@Nullable InfoWindowAdapter infoWindowAdapter) { - infoWindowManager.setInfoWindowAdapter(infoWindowAdapter); + annotationManager.getInfoWindowManager().setInfoWindowAdapter(infoWindowAdapter); } /** @@ -1219,7 +1311,7 @@ public class MapboxMap { @UiThread @Nullable public InfoWindowAdapter getInfoWindowAdapter() { - return infoWindowManager.getInfoWindowAdapter(); + return annotationManager.getInfoWindowManager().getInfoWindowAdapter(); } /** @@ -1229,7 +1321,7 @@ public class MapboxMap { */ @UiThread public void setAllowConcurrentMultipleOpenInfoWindows(boolean allow) { - infoWindowManager.setAllowConcurrentMultipleOpenInfoWindows(allow); + annotationManager.getInfoWindowManager().setAllowConcurrentMultipleOpenInfoWindows(allow); } /** @@ -1239,12 +1331,12 @@ public class MapboxMap { */ @UiThread public boolean isAllowConcurrentMultipleOpenInfoWindows() { - return infoWindowManager.isAllowConcurrentMultipleOpenInfoWindows(); + return annotationManager.getInfoWindowManager().isAllowConcurrentMultipleOpenInfoWindows(); } // Internal API List<InfoWindow> getInfoWindows() { - return infoWindowManager.getInfoWindows(); + return annotationManager.getInfoWindowManager().getInfoWindows(); } AnnotationManager getAnnotationManager() { @@ -1279,7 +1371,7 @@ public class MapboxMap { * @param bottom The bottom margin in pixels. */ public void setPadding(int left, int top, int right, int bottom) { - mapView.setContentPadding(left, top, right, bottom); + projection.setContentPadding(new int[]{left, top, right, bottom}, myLocationViewSettings.getPadding()); uiSettings.invalidate(); } @@ -1289,10 +1381,7 @@ public class MapboxMap { * @return An array with length 4 in the LTRB order. */ public int[] getPadding() { - return new int[]{mapView.getContentPaddingLeft(), - mapView.getContentPaddingTop(), - mapView.getContentPaddingRight(), - mapView.getContentPaddingBottom()}; + return projection.getContentPadding(); } // @@ -1334,12 +1423,7 @@ public class MapboxMap { */ @UiThread public void setOnScrollListener(@Nullable OnScrollListener listener) { - onScrollListener = listener; - } - - // used by MapView - OnScrollListener getOnScrollListener() { - return onScrollListener; + onRegisterTouchListener.onRegisterScrollListener(listener); } /** @@ -1350,12 +1434,7 @@ public class MapboxMap { */ @UiThread public void setOnFlingListener(@Nullable OnFlingListener listener) { - onFlingListener = listener; - } - - // used by MapView - OnFlingListener getOnFlingListener() { - return onFlingListener; + onRegisterTouchListener.onRegisterFlingListener(listener); } /** @@ -1366,12 +1445,7 @@ public class MapboxMap { */ @UiThread public void setOnMapClickListener(@Nullable OnMapClickListener listener) { - onMapClickListener = listener; - } - - // used by MapView - OnMapClickListener getOnMapClickListener() { - return onMapClickListener; + onRegisterTouchListener.onRegisterMapClickListener(listener); } /** @@ -1382,12 +1456,7 @@ public class MapboxMap { */ @UiThread public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) { - onMapLongClickListener = listener; - } - - // used by MapView - OnMapLongClickListener getOnMapLongClickListener() { - return onMapLongClickListener; + onRegisterTouchListener.onRegisterMapLongClickListener(listener); } /** @@ -1398,7 +1467,7 @@ public class MapboxMap { */ @UiThread public void setOnInfoWindowClickListener(@Nullable OnInfoWindowClickListener listener) { - infoWindowManager.setOnInfoWindowClickListener(listener); + getAnnotationManager().getInfoWindowManager().setOnInfoWindowClickListener(listener); } /** @@ -1408,7 +1477,7 @@ public class MapboxMap { */ @UiThread public OnInfoWindowClickListener getOnInfoWindowClickListener() { - return infoWindowManager.getOnInfoWindowClickListener(); + return getAnnotationManager().getInfoWindowManager().getOnInfoWindowClickListener(); } /** @@ -1420,7 +1489,7 @@ public class MapboxMap { @UiThread public void setOnInfoWindowLongClickListener(@Nullable OnInfoWindowLongClickListener listener) { - infoWindowManager.setOnInfoWindowLongClickListener(listener); + getAnnotationManager().getInfoWindowManager().setOnInfoWindowLongClickListener(listener); } /** @@ -1429,11 +1498,11 @@ public class MapboxMap { * @return Current active InfoWindow long Click Listener */ public OnInfoWindowLongClickListener getOnInfoWindowLongClickListener() { - return infoWindowManager.getOnInfoWindowLongClickListener(); + return getAnnotationManager().getInfoWindowManager().getOnInfoWindowLongClickListener(); } public void setOnInfoWindowCloseListener(@Nullable OnInfoWindowCloseListener listener) { - infoWindowManager.setOnInfoWindowCloseListener(listener); + getAnnotationManager().getInfoWindowManager().setOnInfoWindowCloseListener(listener); } /** @@ -1443,7 +1512,7 @@ public class MapboxMap { */ @UiThread public OnInfoWindowCloseListener getOnInfoWindowCloseListener() { - return infoWindowManager.getOnInfoWindowCloseListener(); + return getAnnotationManager().getInfoWindowManager().getOnInfoWindowCloseListener(); } // @@ -1474,13 +1543,13 @@ public class MapboxMap { */ @UiThread public void setMyLocationEnabled(boolean enabled) { - if (!mapView.isPermissionsAccepted()) { + if (!trackingSettings.isPermissionsAccepted()) { Timber.e("Could not activate user location tracking: " + "user did not accept the permission or permissions were not requested."); return; } myLocationEnabled = enabled; - mapView.setMyLocationEnabled(enabled); + trackingSettings.setMyLocationEnabled(enabled); } /** @@ -1491,7 +1560,7 @@ public class MapboxMap { @UiThread @Nullable public Location getMyLocation() { - return mapView.getMyLocation(); + return trackingSettings.getMyLocation(); } /** @@ -1504,7 +1573,7 @@ public class MapboxMap { @UiThread public void setOnMyLocationChangeListener(@Nullable MapboxMap.OnMyLocationChangeListener listener) { - mapView.setOnMyLocationChangeListener(listener); + trackingSettings.setOnMyLocationChangeListener(listener); } /** @@ -1514,15 +1583,8 @@ public class MapboxMap { * To unset the callback, use null. */ @UiThread - public void setOnMyLocationTrackingModeChangeListener - (@Nullable MapboxMap.OnMyLocationTrackingModeChangeListener listener) { - onMyLocationTrackingModeChangeListener = listener; - } - - // used by MapView - MapboxMap.OnMyLocationTrackingModeChangeListener getOnMyLocationTrackingModeChangeListener - () { - return onMyLocationTrackingModeChangeListener; + public void setOnMyLocationTrackingModeChangeListener(@Nullable MapboxMap.OnMyLocationTrackingModeChangeListener listener) { + trackingSettings.setOnMyLocationTrackingModeChangeListener(listener); } /** @@ -1532,26 +1594,8 @@ public class MapboxMap { * To unset the callback, use null. */ @UiThread - public void setOnMyBearingTrackingModeChangeListener - (@Nullable OnMyBearingTrackingModeChangeListener listener) { - onMyBearingTrackingModeChangeListener = listener; - } - - // used by MapView - OnMyBearingTrackingModeChangeListener getOnMyBearingTrackingModeChangeListener() { - return onMyBearingTrackingModeChangeListener; - } - - MapView getMapView() { - return mapView; - } - - void setUiSettings(UiSettings uiSettings) { - this.uiSettings = uiSettings; - } - - void setProjection(Projection projection) { - this.projection = projection; + public void setOnMyBearingTrackingModeChangeListener(@Nullable OnMyBearingTrackingModeChangeListener listener) { + trackingSettings.setOnMyBearingTrackingModeChangeListener(listener); } // @@ -1559,13 +1603,6 @@ public class MapboxMap { // /** - * Triggers an invalidation of the map view. - */ - public void invalidate() { - mapView.invalidate(); - } - - /** * Takes a snapshot of the map. * * @param callback Callback method invoked when the snapshot is taken. @@ -1573,7 +1610,8 @@ public class MapboxMap { */ @UiThread public void snapshot(@NonNull SnapshotReadyCallback callback, @Nullable final Bitmap bitmap) { - mapView.snapshot(callback, bitmap); + // FIXME 12/02/2016 + //mapView.snapshot(callback, bitmap); } /** @@ -1583,7 +1621,8 @@ public class MapboxMap { */ @UiThread public void snapshot(@NonNull SnapshotReadyCallback callback) { - mapView.snapshot(callback, null); + // FIXME 12/02/2016 + //mapView.snapshot(callback, null); } /** @@ -1597,7 +1636,7 @@ public class MapboxMap { @NonNull public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... layerIds) { - return mapView.getNativeMapView().queryRenderedFeatures(coordinates, layerIds); + return nativeMapView.queryRenderedFeatures(coordinates, layerIds); } /** @@ -1611,21 +1650,7 @@ public class MapboxMap { @NonNull 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(); - } - } + return nativeMapView.queryRenderedFeatures(coordinates, layerIds); } // @@ -1685,6 +1710,20 @@ public class MapboxMap { } /** + * Interface definition for a callback to be invoked when a user registers an listener that is + * related to touch and click events. + */ + interface OnRegisterTouchListener { + void onRegisterMapClickListener(OnMapClickListener listener); + + void onRegisterMapLongClickListener(OnMapLongClickListener listener); + + void onRegisterScrollListener(OnScrollListener listener); + + void onRegisterFlingListener(OnFlingListener listener); + } + + /** * Interface definition for a callback to be invoked when the user clicks on the map view. * * @see MapboxMap#setOnMapClickListener(OnMapClickListener) @@ -1939,7 +1978,7 @@ public class MapboxMap { /** * Interface definition for a callback to be invoked when the the My Location tracking mode changes. * - * @see MapView#setMyLocationTrackingMode(int) + * @see TrackingSettings#setMyLocationTrackingMode(int) */ public interface OnMyLocationTrackingModeChangeListener { @@ -1954,7 +1993,7 @@ public class MapboxMap { /** * Interface definition for a callback to be invoked when the the My Location tracking mode changes. * - * @see MapView#setMyLocationTrackingMode(int) + * @see TrackingSettings#setMyLocationTrackingMode(int) */ public interface OnMyBearingTrackingModeChangeListener { 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 c3d12c4a7a..6189087686 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 @@ -9,14 +9,12 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.DisplayMetrics; -import timber.log.Timber; import android.view.Surface; import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.Polyline; -import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.ProjectedMeters; import com.mapbox.mapboxsdk.offline.OfflineManager; @@ -32,10 +30,13 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import timber.log.Timber; + // Class that wraps the native methods for convenience final class NativeMapView { - boolean destroyed = false; + // Flag to indicating destroy was called + private boolean destroyed = false; // Holds the pointer to JNI NativeMapView private long nativeMapViewPtr = 0; @@ -43,8 +44,10 @@ final class NativeMapView { // Used for callbacks private MapView mapView; + // Device density private final float pixelRatio; + // Listeners for Map change events private CopyOnWriteArrayList<MapView.OnMapChangedListener> onMapChangedListeners; // @@ -139,6 +142,9 @@ final class NativeMapView { } public void resizeView(int width, int height) { + width = (int) (width / pixelRatio); + height = (int) (height / pixelRatio); + if (width < 0) { throw new IllegalArgumentException("width cannot be negative."); } @@ -208,6 +214,10 @@ final class NativeMapView { nativeSetStyleUrl(nativeMapViewPtr, url); } + public String getStyleUrl() { + return nativeGetStyleUrl(nativeMapViewPtr); + } + public void setStyleJson(String newStyleJson) { nativeSetStyleJson(nativeMapViewPtr, newStyleJson); } @@ -237,7 +247,7 @@ final class NativeMapView { } public void moveBy(double dx, double dy, long duration) { - nativeMoveBy(nativeMapViewPtr, dx, dy, duration); + nativeMoveBy(nativeMapViewPtr, dx / pixelRatio, dy / pixelRatio, duration); } public void setLatLng(LatLng latLng) { @@ -273,7 +283,7 @@ final class NativeMapView { } public void scaleBy(double ds, double cx, double cy, long duration) { - nativeScaleBy(nativeMapViewPtr, ds, cx, cy, duration); + nativeScaleBy(nativeMapViewPtr, ds, cx / pixelRatio, cy / pixelRatio, duration); } public void setScale(double scale) { @@ -285,7 +295,7 @@ final class NativeMapView { } public void setScale(double scale, double cx, double cy, long duration) { - nativeSetScale(nativeMapViewPtr, scale, cx, cy, duration); + nativeSetScale(nativeMapViewPtr, scale, cx / pixelRatio, cy / pixelRatio, duration); } public double getScale() { @@ -330,11 +340,15 @@ final class NativeMapView { public void rotateBy(double sx, double sy, double ex, double ey, long duration) { - nativeRotateBy(nativeMapViewPtr, sx, sy, ex, ey, duration); + nativeRotateBy(nativeMapViewPtr, sx/pixelRatio, sy/pixelRatio, ex, ey, duration); } - public void setContentPadding(double top, double left, double bottom, double right) { - nativeSetContentPadding(nativeMapViewPtr, top, left, bottom, right); + public void setContentPadding(int[] padding) { + nativeSetContentPadding(nativeMapViewPtr, + padding[1] / pixelRatio, + padding[0] / pixelRatio, + padding[3] / pixelRatio, + padding[2] / pixelRatio); } public void setBearing(double degrees) { @@ -346,7 +360,7 @@ final class NativeMapView { } public void setBearing(double degrees, double cx, double cy) { - nativeSetBearingXY(nativeMapViewPtr, degrees, cx, cy); + nativeSetBearingXY(nativeMapViewPtr, degrees, cx/pixelRatio, cy/pixelRatio); } public double getBearing() { @@ -443,8 +457,8 @@ final class NativeMapView { nativeSetReachability(nativeMapViewPtr, status); } - public double getMetersPerPixelAtLatitude(double lat, double zoom) { - return nativeGetMetersPerPixelAtLatitude(nativeMapViewPtr, lat, zoom); + public double getMetersPerPixelAtLatitude(double lat) { + return nativeGetMetersPerPixelAtLatitude(nativeMapViewPtr, lat, getZoom()); } public ProjectedMeters projectedMetersForLatLng(LatLng latLng) { @@ -456,11 +470,13 @@ final class NativeMapView { } public PointF pixelForLatLng(LatLng latLng) { - return nativePixelForLatLng(nativeMapViewPtr, latLng.getLatitude(), latLng.getLongitude()); + PointF pointF = nativePixelForLatLng(nativeMapViewPtr, latLng.getLatitude(), latLng.getLongitude()); + pointF.set(pointF.x * pixelRatio, pointF.y * pixelRatio); + return pointF; } public LatLng latLngForPixel(PointF pixel) { - return nativeLatLngForPixel(nativeMapViewPtr, pixel.x, pixel.y); + return nativeLatLngForPixel(nativeMapViewPtr, pixel.x / pixelRatio, pixel.y / pixelRatio); } public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) { @@ -636,6 +652,8 @@ final class NativeMapView { private native void nativeSetStyleUrl(long nativeMapViewPtr, String url); + private native String nativeGetStyleUrl(long nativeMapViewPtr); + private native void nativeSetStyleJson(long nativeMapViewPtr, String newStyleJson); private native String nativeGetStyleJson(long nativeMapViewPtr); @@ -782,6 +800,14 @@ final class NativeMapView { private native void nativeSetAPIBaseURL(long nativeMapViewPtr, String baseUrl); + int getWidth() { + return mapView.getWidth(); + } + + int getHeight() { + return mapView.getHeight(); + } + // // MapChangeEvents // diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java index e06ed38433..5a28d0c7a0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java @@ -7,6 +7,7 @@ import android.support.annotation.NonNull; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.geometry.VisibleRegion; +import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; /** * A projection is used to translate between on screen location and geographic coordinates on @@ -15,15 +16,33 @@ import com.mapbox.mapboxsdk.geometry.VisibleRegion; */ public class Projection { - private final MapView mapView; - private final float screenDensity; - private final PointF screenLocationPoint; + private final NativeMapView nativeMapView; + private int[] contentPadding; - Projection(@NonNull MapView mapView) { - this.mapView = mapView; - this.screenLocationPoint = new PointF(); - this.screenDensity = mapView.getContext() != null ? /* return default if unit test */ - mapView.getContext().getResources().getDisplayMetrics().density : 1.0f; + Projection(@NonNull NativeMapView nativeMapView) { + this.nativeMapView = nativeMapView; + this.contentPadding = new int[]{0, 0, 0, 0}; + } + + void setContentPadding(int[] contentPadding, int[] userLocationViewPadding) { + this.contentPadding = contentPadding; + + int[] padding = new int[]{ + contentPadding[0] + userLocationViewPadding[0], + contentPadding[1] + userLocationViewPadding[1], + contentPadding[2] + userLocationViewPadding[2], + contentPadding[3] + userLocationViewPadding[3] + }; + + nativeMapView.setContentPadding(padding); + } + + int[] getContentPadding() { + return contentPadding; + } + + public void invalidateContentPadding(int[] userLocationViewPadding) { + setContentPadding(contentPadding, userLocationViewPadding); } /** @@ -37,7 +56,7 @@ public class Projection { * @return The distance measured in meters. */ public double getMetersPerPixelAtLatitude(@FloatRange(from = -90, to = 90) double latitude) { - return mapView.getMetersPerPixelAtLatitude(latitude); + return nativeMapView.getMetersPerPixelAtLatitude(latitude); } /** @@ -50,8 +69,7 @@ public class Projection { * the given screen point does not intersect the ground plane. */ public LatLng fromScreenLocation(PointF point) { - screenLocationPoint.set(point.x / screenDensity, point.y / screenDensity); - return mapView.fromNativeScreenLocation(screenLocationPoint); + return nativeMapView.latLngForPixel(point); } /** @@ -63,10 +81,10 @@ public class Projection { public VisibleRegion getVisibleRegion() { LatLngBounds.Builder builder = new LatLngBounds.Builder(); - float left = mapView.getContentPaddingLeft(); - float right = mapView.getWidth() - mapView.getContentPaddingRight(); - float top = mapView.getContentPaddingTop(); - float bottom = mapView.getHeight() - mapView.getContentPaddingBottom(); + float left = contentPadding[0]; + float right = nativeMapView.getWidth() - contentPadding[2]; + float top = contentPadding[1]; + float bottom = nativeMapView.getHeight() - contentPadding[3]; LatLng topLeft = fromScreenLocation(new PointF(left, top)); LatLng topRight = fromScreenLocation(new PointF(right, top)); @@ -90,18 +108,14 @@ public class Projection { * @return A Point representing the screen location in screen pixels. */ public PointF toScreenLocation(LatLng location) { - PointF pointF = mapView.toNativeScreenLocation(location); - pointF.set(pointF.x * screenDensity, pointF.y * screenDensity); - return pointF; + return nativeMapView.pixelForLatLng(location); } - /** - * Calculates a zoom level based on minimum scale and current scale from MapView - * - * @param minScale The minimum scale to calculate the zoom level. - * @return zoom level that fits the MapView. - */ - public double calculateZoom(float minScale) { - return Math.log(mapView.getScale() * minScale) / Math.log(2); + float getHeight() { + return nativeMapView.getHeight(); + } + + float getWidth() { + return nativeMapView.getWidth(); } } 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 ac620ca7ba..560f7375c9 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 @@ -1,22 +1,30 @@ package com.mapbox.mapboxsdk.maps; +import android.Manifest; +import android.content.pm.PackageManager; +import android.graphics.PointF; +import android.location.Location; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; +import com.mapbox.mapboxsdk.location.LocationListener; +import com.mapbox.mapboxsdk.location.LocationServices; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; /** * Settings for the user location and bearing tracking of a MapboxMap. */ -public class TrackingSettings { +public final class TrackingSettings { - private MapView mapView; - private UiSettings uiSettings; - private boolean dismissLocationTrackingOnGesture = true; - private boolean dismissBearingTrackingOnGesture = true; + private final MyLocationView myLocationView; + private final UiSettings uiSettings; + private final FocalPointChangeListener focalPointChangedListener; + private LocationListener myLocationListener; @MyLocationTracking.Mode private int myLocationTrackingMode; @@ -24,8 +32,14 @@ public class TrackingSettings { @MyBearingTracking.Mode private int myBearingTrackingMode; - TrackingSettings(@NonNull MapView mapView, UiSettings uiSettings) { - this.mapView = mapView; + private MapboxMap.OnMyLocationTrackingModeChangeListener onMyLocationTrackingModeChangeListener; + private MapboxMap.OnMyBearingTrackingModeChangeListener onMyBearingTrackingModeChangeListener; + private boolean dismissLocationTrackingOnGesture = true; + private boolean dismissBearingTrackingOnGesture = true; + + TrackingSettings(@NonNull MyLocationView myLocationView, UiSettings uiSettings, FocalPointChangeListener focalPointChangedListener) { + this.myLocationView = myLocationView; + this.focalPointChangedListener = focalPointChangedListener; this.uiSettings = uiSettings; } @@ -45,7 +59,17 @@ public class TrackingSettings { @UiThread public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) { this.myLocationTrackingMode = myLocationTrackingMode; - mapView.setMyLocationTrackingMode(myLocationTrackingMode); + myLocationView.setMyLocationTrackingMode(myLocationTrackingMode); + + if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) { + focalPointChangedListener.onFocalPointChanged(new PointF(myLocationView.getCenterX(), myLocationView.getCenterY())); + } else { + focalPointChangedListener.onFocalPointChanged(null); + } + + if (onMyLocationTrackingModeChangeListener != null) { + onMyLocationTrackingModeChangeListener.onMyLocationTrackingModeChange(myLocationTrackingMode); + } } /** @@ -79,7 +103,10 @@ public class TrackingSettings { @UiThread public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) { this.myBearingTrackingMode = myBearingTrackingMode; - mapView.setMyBearingTrackingMode(myBearingTrackingMode); + myLocationView.setMyBearingTrackingMode(myBearingTrackingMode); + if (onMyBearingTrackingModeChangeListener != null) { + onMyBearingTrackingModeChangeListener.onMyBearingTrackingModeChange(myBearingTrackingMode); + } } /** @@ -220,7 +247,7 @@ public class TrackingSettings { * @param translate * @param rotate */ - public void resetTrackingModesIfRequired(boolean translate, boolean rotate) { + 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); @@ -232,7 +259,57 @@ public class TrackingSettings { } } - public void resetTrackingModesIfRequired(CameraPosition cameraPosition) { + void resetTrackingModesIfRequired(CameraPosition cameraPosition) { resetTrackingModesIfRequired(cameraPosition.target != null, cameraPosition.bearing != -1); } + + public void invalidateFocalPointForTracking(MyLocationView myLocationView) { + if (!isLocationTrackingDisabled()) { + focalPointChangedListener.onFocalPointChanged(new PointF(myLocationView.getCenterX(), myLocationView.getCenterY())); + } else { + focalPointChangedListener.onFocalPointChanged(null); + } + } + + void setMyLocationEnabled(boolean enabled) { + myLocationView.setEnabled(enabled); + } + + Location getMyLocation() { + return myLocationView.getLocation(); + } + + void setOnMyLocationChangeListener(@Nullable final MapboxMap.OnMyLocationChangeListener listener) { + if (listener != null) { + myLocationListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + if (listener != null) { + listener.onMyLocationChange(location); + } + } + }; + LocationServices.getLocationServices(myLocationView.getContext()).addLocationListener(myLocationListener); + } else { + LocationServices.getLocationServices(myLocationView.getContext()).removeLocationListener(myLocationListener); + myLocationListener = null; + } + } + + boolean isPermissionsAccepted() { + return (ContextCompat.checkSelfPermission(myLocationView.getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) || + ContextCompat.checkSelfPermission(myLocationView.getContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; + } + + void setOnMyLocationTrackingModeChangeListener(MapboxMap.OnMyLocationTrackingModeChangeListener onMyLocationTrackingModeChangeListener) { + this.onMyLocationTrackingModeChangeListener = onMyLocationTrackingModeChangeListener; + } + + void setOnMyBearingTrackingModeChangeListener(MapboxMap.OnMyBearingTrackingModeChangeListener onMyBearingTrackingModeChangeListener) { + this.onMyBearingTrackingModeChangeListener = onMyBearingTrackingModeChangeListener; + } + + MyLocationView getMyLocationView(){ + return myLocationView; + } } 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 1532bb6745..4aefb0bb7e 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 @@ -1,10 +1,15 @@ package com.mapbox.mapboxsdk.maps; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; +import com.mapbox.mapboxsdk.constants.MapboxConstants; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import java.util.concurrent.TimeUnit; @@ -16,18 +21,23 @@ import static com.mapbox.mapboxsdk.maps.MapView.REGION_DID_CHANGE_ANIMATED; * Responsible for synchronising {@link CameraPosition} state and notifying {@link com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraChangeListener}. * </p> */ -class Transform { +final class Transform implements MapView.OnMapChangedListener { - private NativeMapView mapView; - private MapboxMap mapboxMap; + private final NativeMapView mapView; + private final MarkerViewManager markerViewManager; + private final TrackingSettings trackingSettings; + private final MyLocationView myLocationView; private CameraPosition cameraPosition; private MapboxMap.CancelableCallback cameraCancelableCallback; private MapboxMap.OnCameraChangeListener onCameraChangeListener; - Transform(NativeMapView mapView, MapboxMap mapboxMap) { + Transform(NativeMapView mapView, MarkerViewManager markerViewManager, TrackingSettings trackingSettings) { this.mapView = mapView; - this.mapboxMap = mapboxMap; + this.markerViewManager = markerViewManager; + this.trackingSettings = trackingSettings; + this.myLocationView = trackingSettings.getMyLocationView(); + mapView.addOnMapChangedListener(this); } // @@ -43,25 +53,36 @@ class Transform { } @UiThread - final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { + void updateCameraPosition(@NonNull CameraPosition position) { + if (myLocationView != null) { + myLocationView.setCameraPosition(position); + } + markerViewManager.setTilt((float) position.tilt); + } + + @Override + public void onMapChanged(@MapView.MapChange int change) { + if (change == MapView.REGION_DID_CHANGE_ANIMATED) { + invalidateCameraPosition(); + } + } + + @UiThread + final void moveCamera(MapboxMap mapboxMap, CameraUpdate update, MapboxMap.CancelableCallback callback) { cameraPosition = update.getCameraPosition(mapboxMap); - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(cameraPosition); + trackingSettings.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) { + final void easeCamera(MapboxMap mapboxMap, CameraUpdate update, int durationMs, boolean easingInterpolator, boolean resetTrackingMode, final MapboxMap.CancelableCallback callback) { cameraPosition = update.getCameraPosition(mapboxMap); if (resetTrackingMode) { - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(cameraPosition); + trackingSettings.resetTrackingModesIfRequired(cameraPosition); } cancelTransitions(); @@ -83,9 +104,9 @@ class Transform { } @UiThread - final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { + final void animateCamera(MapboxMap mapboxMap, CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { cameraPosition = update.getCameraPosition(mapboxMap); - mapboxMap.getTrackingSettings().resetTrackingModesIfRequired(cameraPosition); + trackingSettings.resetTrackingModesIfRequired(cameraPosition); cancelTransitions(); if (callback != null) { @@ -140,4 +161,110 @@ class Transform { private long getDurationNano(long durationMs) { return durationMs > 0 ? TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS) : 0; } + + // + // non Camera API + // + + // Zoom in or out + + double getZoom() { + return cameraPosition.zoom; + } + + void zoom(boolean zoomIn) { + zoom(zoomIn, -1.0f, -1.0f); + } + + void zoom(boolean zoomIn, float x, float y) { + // Cancel any animation + cancelTransitions(); + + if (zoomIn) { + mapView.scaleBy(2.0, x, y, MapboxConstants.ANIMATION_DURATION); + } else { + mapView.scaleBy(0.5, x, y, MapboxConstants.ANIMATION_DURATION); + } + } + + // Direction + double getBearing() { + double direction = -mapView.getBearing(); + + while (direction > 360) { + direction -= 360; + } + while (direction < 0) { + direction += 360; + } + + return direction; + } + + double getRawBearing(){ + return mapView.getBearing(); + } + + void setBearing(double bearing) { + if (myLocationView != null) { + myLocationView.setBearing(bearing); + } + mapView.setBearing(bearing); + } + + void setBearing(double bearing, float focalX, float focalY) { + if (myLocationView != null) { + myLocationView.setBearing(bearing); + } + mapView.setBearing(bearing, focalX, focalY); + } + + + // + // LatLng / CenterCoordinate + // + + LatLng getLatLng() { + return mapView.getLatLng(); + } + + // + // Pitch / Tilt + // + + double getTilt() { + return mapView.getPitch(); + } + + void setTilt(Double pitch) { + if (myLocationView != null) { + myLocationView.setTilt(pitch); + } + markerViewManager.setTilt(pitch.floatValue()); + mapView.setPitch(pitch, 0); + } + + // + // Center coordinate + // + + LatLng getCenterCoordinate() { + return mapView.getLatLng(); + } + + void setCenterCoordinate(LatLng centerCoordinate) { + mapView.setLatLng(centerCoordinate); + } + + void setGestureInProgress(boolean gestureInProgress) { + mapView.setGestureInProgress(gestureInProgress); + } + + void zoomBy(double pow, float x, float y) { + mapView.scaleBy(pow, x, y); + } + + void moveBy(double offsetX, double offsetY, long duration) { + mapView.moveBy(offsetX, offsetY, duration); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java index d6ad80d11e..1523fcefa7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java @@ -1,23 +1,32 @@ package com.mapbox.mapboxsdk.maps; +import android.graphics.Color; import android.graphics.PointF; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; import android.view.Gravity; import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.maps.widgets.CompassView; +import com.mapbox.mapboxsdk.utils.ColorUtils; /** * Settings for the user interface of a MapboxMap. To obtain this interface, call getUiSettings(). */ -public class UiSettings { - - private MapView mapView; +public final class UiSettings { - private CompassViewSettings compassSettings; - private ViewSettings logoSettings; - private ViewSettings attributionSettings; + private final FocalPointChangeListener focalPointChangeListener; + private final Projection projection; + private final CompassView compassView; + private final ImageView attributionsView; + private final View logoView; + private float pixelRatio; private boolean rotateGesturesEnabled = true; private boolean rotateGestureChangeAllowed = true; @@ -35,13 +44,17 @@ public class UiSettings { private boolean deselectMarkersOnTap = true; - private PointF focalPoint; + private PointF userProvidedFocalPoint; - UiSettings(@NonNull MapView mapView) { - this.mapView = mapView; - this.compassSettings = new CompassViewSettings(); - this.logoSettings = new ViewSettings(); - this.attributionSettings = new ViewSettings(); + UiSettings(@NonNull Projection projection, @NonNull FocalPointChangeListener listener, @NonNull CompassView compassView, @NonNull ImageView attributionsView, @NonNull View logoView) { + this.projection = projection; + this.focalPointChangeListener = listener; + this.compassView = compassView; + this.attributionsView = attributionsView; + this.logoView = logoView; + if(logoView.getResources()!=null) { + this.pixelRatio = logoView.getResources().getDisplayMetrics().density; + } } /** @@ -56,8 +69,7 @@ public class UiSettings { * @param compassEnabled True to enable the compass; false to disable the compass. */ public void setCompassEnabled(boolean compassEnabled) { - compassSettings.setEnabled(compassEnabled); - mapView.setCompassEnabled(compassEnabled); + compassView.setEnabled(compassEnabled); } /** @@ -66,7 +78,7 @@ public class UiSettings { * @return True if the compass is enabled; false if the compass is disabled. */ public boolean isCompassEnabled() { - return compassSettings.isEnabled(); + return compassView.isEnabled(); } /** @@ -81,8 +93,7 @@ public class UiSettings { */ @UiThread public void setCompassGravity(int gravity) { - compassSettings.setGravity(gravity); - mapView.setCompassGravity(gravity); + setWidgetGravity(compassView, gravity); } /** @@ -94,8 +105,7 @@ public class UiSettings { * @param compassFadeFacingNorth True to enable the fading animation; false to disable it */ public void setCompassFadeFacingNorth(boolean compassFadeFacingNorth) { - compassSettings.setFadeFacingNorth(compassFadeFacingNorth); - mapView.setCompassFadeFacingNorth(compassFadeFacingNorth); + compassView.fadeCompassViewFacingNorth(compassFadeFacingNorth); } /** @@ -103,8 +113,8 @@ public class UiSettings { * * @return True if the compass will fade, false if it remains visible */ - public boolean isCompassFadeWhenFacingNorth(){ - return compassSettings.isFadeFacingNorth(); + public boolean isCompassFadeWhenFacingNorth() { + return compassView.isFadeCompassViewFacingNorth(); } /** @@ -113,7 +123,7 @@ public class UiSettings { * @return The gravity */ public int getCompassGravity() { - return compassSettings.getGravity(); + return ((FrameLayout.LayoutParams) compassView.getLayoutParams()).gravity; } /** @@ -127,8 +137,7 @@ public class UiSettings { */ @UiThread public void setCompassMargins(int left, int top, int right, int bottom) { - compassSettings.setMargins(new int[]{left, top, right, bottom}); - mapView.setCompassMargins(left, top, right, bottom); + setWidgetMargins(compassView, left, top, right, bottom); } /** @@ -137,7 +146,7 @@ public class UiSettings { * @return The left margin in pixels */ public int getCompassMarginLeft() { - return compassSettings.getMargins()[0]; + return ((FrameLayout.LayoutParams) compassView.getLayoutParams()).leftMargin; } /** @@ -146,7 +155,7 @@ public class UiSettings { * @return The top margin in pixels */ public int getCompassMarginTop() { - return compassSettings.getMargins()[1]; + return ((FrameLayout.LayoutParams) compassView.getLayoutParams()).topMargin; } /** @@ -155,7 +164,7 @@ public class UiSettings { * @return The right margin in pixels */ public int getCompassMarginRight() { - return compassSettings.getMargins()[2]; + return ((FrameLayout.LayoutParams) compassView.getLayoutParams()).rightMargin; } /** @@ -164,7 +173,7 @@ public class UiSettings { * @return The bottom margin in pixels */ public int getCompassMarginBottom() { - return compassSettings.getMargins()[3]; + return ((FrameLayout.LayoutParams) compassView.getLayoutParams()).bottomMargin; } /** @@ -176,8 +185,7 @@ public class UiSettings { * @param enabled True to enable the logo; false to disable the logo. */ public void setLogoEnabled(boolean enabled) { - logoSettings.setEnabled(enabled); - mapView.setLogoEnabled(enabled); + logoView.setVisibility(enabled ? View.VISIBLE : View.GONE); } /** @@ -186,7 +194,7 @@ public class UiSettings { * @return True if the logo is enabled; false if the logo is disabled. */ public boolean isLogoEnabled() { - return logoSettings.isEnabled(); + return logoView.getVisibility() == View.VISIBLE; } /** @@ -200,8 +208,7 @@ public class UiSettings { * @see Gravity */ public void setLogoGravity(int gravity) { - logoSettings.setGravity(gravity); - mapView.setLogoGravity(gravity); + setWidgetGravity(logoView, gravity); } /** @@ -210,7 +217,7 @@ public class UiSettings { * @return The gravity */ public int getLogoGravity() { - return logoSettings.getGravity(); + return ((FrameLayout.LayoutParams) logoView.getLayoutParams()).gravity; } /** @@ -223,8 +230,7 @@ public class UiSettings { * @param bottom The bottom margin in pixels. */ public void setLogoMargins(int left, int top, int right, int bottom) { - logoSettings.setMargins(new int[]{left, top, right, bottom}); - mapView.setLogoMargins(left, top, right, bottom); + setWidgetMargins(logoView, left, top, right, bottom); } /** @@ -233,7 +239,7 @@ public class UiSettings { * @return The left margin in pixels */ public int getLogoMarginLeft() { - return logoSettings.getMargins()[0]; + return ((FrameLayout.LayoutParams) logoView.getLayoutParams()).leftMargin; } /** @@ -242,7 +248,7 @@ public class UiSettings { * @return The top margin in pixels */ public int getLogoMarginTop() { - return logoSettings.getMargins()[1]; + return ((FrameLayout.LayoutParams) logoView.getLayoutParams()).topMargin; } /** @@ -251,7 +257,7 @@ public class UiSettings { * @return The right margin in pixels */ public int getLogoMarginRight() { - return logoSettings.getMargins()[2]; + return ((FrameLayout.LayoutParams) logoView.getLayoutParams()).rightMargin; } /** @@ -260,7 +266,7 @@ public class UiSettings { * @return The bottom margin in pixels */ public int getLogoMarginBottom() { - return logoSettings.getMargins()[3]; + return ((FrameLayout.LayoutParams) logoView.getLayoutParams()).bottomMargin; } /** @@ -272,8 +278,7 @@ public class UiSettings { * @param enabled True to enable the attribution; false to disable the attribution. */ public void setAttributionEnabled(boolean enabled) { - attributionSettings.setEnabled(enabled); - mapView.setAttributionEnabled(enabled ? View.VISIBLE : View.GONE); + attributionsView.setVisibility(enabled ? View.VISIBLE : View.GONE); } /** @@ -282,7 +287,7 @@ public class UiSettings { * @return True if the attribution is enabled; false if the attribution is disabled. */ public boolean isAttributionEnabled() { - return attributionSettings.isEnabled(); + return attributionsView.getVisibility() == View.VISIBLE; } /** @@ -295,8 +300,7 @@ public class UiSettings { * @see Gravity */ public void setAttributionGravity(int gravity) { - attributionSettings.setGravity(gravity); - mapView.setAttributionGravity(gravity); + setWidgetGravity(attributionsView, gravity); } /** @@ -305,7 +309,7 @@ public class UiSettings { * @return The gravity */ public int getAttributionGravity() { - return attributionSettings.getGravity(); + return ((FrameLayout.LayoutParams) attributionsView.getLayoutParams()).gravity; } /** @@ -317,8 +321,7 @@ public class UiSettings { * @param bottom The bottom margin in pixels. */ public void setAttributionMargins(int left, int top, int right, int bottom) { - attributionSettings.setMargins(new int[]{left, top, right, bottom}); - mapView.setAttributionMargins(left, top, right, bottom); + setWidgetMargins(attributionsView, left, top, right, bottom); } /** @@ -330,17 +333,12 @@ public class UiSettings { * @param tintColor Color to tint the attribution. */ public void setAttributionTintColor(@ColorInt int tintColor) { - attributionSettings.setTintColor(tintColor); - mapView.setAtttibutionTintColor(tintColor); - } - - /** - * Returns the tint color value of the attribution view. - * - * @return The tint color - */ - public int getAttributionTintColor() { - return attributionSettings.getTintColor(); + // Check that the tint color being passed in isn't transparent. + if (Color.alpha(tintColor) == 0) { + ColorUtils.setTintList(attributionsView, ContextCompat.getColor(attributionsView.getContext(), R.color.mapbox_blue)); + } else { + ColorUtils.setTintList(attributionsView, tintColor); + } } /** @@ -349,7 +347,7 @@ public class UiSettings { * @return The left margin in pixels */ public int getAttributionMarginLeft() { - return attributionSettings.getMargins()[0]; + return ((FrameLayout.LayoutParams) attributionsView.getLayoutParams()).leftMargin; } /** @@ -358,7 +356,7 @@ public class UiSettings { * @return The top margin in pixels */ public int getAttributionMarginTop() { - return attributionSettings.getMargins()[1]; + return ((FrameLayout.LayoutParams) attributionsView.getLayoutParams()).topMargin; } /** @@ -367,7 +365,7 @@ public class UiSettings { * @return The right margin in pixels */ public int getAttributionMarginRight() { - return attributionSettings.getMargins()[2]; + return ((FrameLayout.LayoutParams) attributionsView.getLayoutParams()).rightMargin; } /** @@ -376,7 +374,7 @@ public class UiSettings { * @return The bottom margin in pixels */ public int getAttributionMarginBottom() { - return attributionSettings.getMargins()[3]; + return ((FrameLayout.LayoutParams) attributionsView.getLayoutParams()).bottomMargin; } /** @@ -593,8 +591,8 @@ public class UiSettings { * @param focalPoint the focal point to be used. */ public void setFocalPoint(@Nullable PointF focalPoint) { - this.focalPoint = focalPoint; - mapView.setFocalPoint(focalPoint); + this.userProvidedFocalPoint = focalPoint; + focalPointChangeListener.onFocalPointChanged(focalPoint); } /** @@ -603,7 +601,7 @@ public class UiSettings { * @return The focal point */ public PointF getFocalPoint() { - return focalPoint; + return userProvidedFocalPoint; } /** @@ -612,7 +610,7 @@ public class UiSettings { * @return height in pixels */ public float getHeight() { - return mapView.getMeasuredHeight(); + return projection.getHeight(); } /** @@ -621,15 +619,36 @@ public class UiSettings { * @return widht in pixels */ public float getWidth() { - return mapView.getMeasuredWidth(); + return projection.getWidth(); + } + + float getPixelRatio() { + return pixelRatio; } /** * Invalidates the ViewSettings instances shown on top of the MapView */ public void invalidate() { - mapView.setLogoMargins(getLogoMarginLeft(), getLogoMarginTop(), getLogoMarginRight(), getLogoMarginBottom()); - mapView.setCompassMargins(getCompassMarginLeft(), getCompassMarginTop(), getCompassMarginRight(), getCompassMarginBottom()); - mapView.setAttributionMargins(getAttributionMarginLeft(), getAttributionMarginTop(), getAttributionMarginRight(), getAttributionMarginBottom()); + setLogoMargins(getLogoMarginLeft(), getLogoMarginTop(), getLogoMarginRight(), getLogoMarginBottom()); + setCompassMargins(getCompassMarginLeft(), getCompassMarginTop(), getCompassMarginRight(), getCompassMarginBottom()); + setAttributionMargins(getAttributionMarginLeft(), getAttributionMarginTop(), getAttributionMarginRight(), getAttributionMarginBottom()); + } + + private void setWidgetGravity(@NonNull final View view, int gravity) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view.getLayoutParams(); + layoutParams.gravity = gravity; + view.setLayoutParams(layoutParams); + } + + private void setWidgetMargins(@NonNull final View view, int left, int top, int right, int bottom) { + int contentPadding[] = projection.getContentPadding(); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view.getLayoutParams(); + left += contentPadding[0]; + top += contentPadding[1]; + right += contentPadding[2]; + bottom += contentPadding[3]; + layoutParams.setMargins(left, top, right, bottom); + view.setLayoutParams(layoutParams); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ViewSettings.java deleted file mode 100644 index 4478978853..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ViewSettings.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.mapbox.mapboxsdk.maps; - -/** - * Settings for the overlain views of a MapboxMap. Used by UiSettings. - */ -class ViewSettings { - - private boolean enabled; - private int gravity; - private int[] margins; - private int tintColor; - - public ViewSettings() { - margins = new int[4]; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public int getGravity() { - return gravity; - } - - public void setGravity(int gravity) { - this.gravity = gravity; - } - - public int[] getMargins() { - return margins; - } - - public void setMargins(int[] margins) { - this.margins = margins; - } - - public int getTintColor() { - return tintColor; - } - - public void setTintColor(int tintColor) { - this.tintColor = tintColor; - } -} 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 5e54354dfa..4d9a60ed66 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 @@ -61,6 +61,7 @@ public final class CompassView extends ImageView implements Runnable { setLayoutParams(lp); } + // TODO refactor MapboxMap and replace with interface public void setMapboxMap(@NonNull MapboxMap mapboxMap) { setOnClickListener(new CompassClickListener(mapboxMap, this)); } @@ -121,6 +122,10 @@ public final class CompassView extends ImageView implements Runnable { fadeCompassViewFacingNorth = compassFadeFacingNorth; } + public boolean isFadeCompassViewFacingNorth(){ + return fadeCompassViewFacingNorth; + } + @Override public void run() { if (isFacingNorth() && fadeCompassViewFacingNorth) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java index 386584141e..1f30734d53 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java @@ -379,6 +379,7 @@ public class MyLocationView extends View { } } + // TODO refactor MapboxMap out public void setMapboxMap(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; this.projection = mapboxMap.getProjection(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java index 80bd1b3bef..d8450ff451 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java @@ -4,14 +4,16 @@ import android.graphics.drawable.Drawable; import android.support.annotation.ColorInt; import android.support.annotation.IntRange; -import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.Projection; +import com.mapbox.mapboxsdk.maps.TrackingSettings; /** * Settings to configure the visual appearance of the MyLocationView. */ public class MyLocationViewSettings { - private MapView mapView; + private Projection projection; + private TrackingSettings trackingSettings; private MyLocationView myLocationView; // @@ -58,13 +60,14 @@ public class MyLocationViewSettings { /** * Creates an instance of MyLocationViewSettings * - * @param mapView the MapView that hosts the MyLocationView + * @param projection the MapView projection * @param myLocationView the MyLocationView to apply the settings to * @see MyLocationView */ - public MyLocationViewSettings(MapView mapView, MyLocationView myLocationView) { - this.mapView = mapView; + public MyLocationViewSettings(Projection projection, MyLocationView myLocationView, TrackingSettings trackingSettings) { + this.projection = projection; this.myLocationView = myLocationView; + this.trackingSettings = trackingSettings; } /** @@ -208,7 +211,8 @@ public class MyLocationViewSettings { public void setPadding(int left, int top, int right, int bottom) { padding = new int[]{left, top, right, bottom}; myLocationView.setContentPadding(padding); - mapView.invalidateContentPadding(); + projection.invalidateContentPadding(padding); + trackingSettings.invalidateFocalPointForTracking(myLocationView); } /** @@ -257,4 +261,8 @@ public class MyLocationViewSettings { this.accuracyTintColor = accuracyTintColor; myLocationView.setAccuracyTint(accuracyTintColor); } + + public void setTilt(double tilt) { + myLocationView.setTilt(tilt); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java index 22e37ec539..a3545df565 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java @@ -1,6 +1,16 @@ package com.mapbox.mapboxsdk.telemetry; +import android.graphics.PointF; +import android.support.annotation.NonNull; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.Projection; + import java.io.Serializable; +import java.util.Hashtable; + +import timber.log.Timber; /** * Constants for Telemetry Metadata @@ -53,7 +63,7 @@ public class MapboxEvent implements Serializable { public static final String ATTRIBUTE_VENDOR_ID = "vendorId"; public static final String ATTRIBUTE_APP_BUNDLE_ID = "appBundleId"; public static final String ATTRIBUTE_MODEL = "model"; - public static final String ATTRIBUTE_OPERATING_SYSTEM= "operatingSystem"; + public static final String ATTRIBUTE_OPERATING_SYSTEM = "operatingSystem"; public static final String ATTRIBUTE_ORIENTATION = "orientation"; public static final String ATTRIBUTE_BATTERY_LEVEL = "batteryLevel"; public static final String ATTRIBUTE_PLUGGED_IN = "pluggedIn"; @@ -63,4 +73,71 @@ public class MapboxEvent implements Serializable { public static final String ATTRIBUTE_CARRIER = "carrier"; public static final String ATTRIBUTE_CELLULAR_NETWORK_TYPE = "cellularNetworkType"; public static final String ATTRIBUTE_WIFI = "wifi"; + + /** + * Helper method for tracking gesture events + * + * @param projection Projection of the Map object + * @param gestureId Type of Gesture See {@see MapboxEvent#GESTURE_SINGLETAP MapboxEvent#GESTURE_DOUBLETAP MapboxEvent#GESTURE_TWO_FINGER_SINGLETAP MapboxEvent#GESTURE_QUICK_ZOOM MapboxEvent#GESTURE_PAN_START MapboxEvent#GESTURE_PINCH_START MapboxEvent#GESTURE_ROTATION_START MapboxEvent#GESTURE_PITCH_START} + * @param xCoordinate Original x screen coordinate at start of gesture + * @param yCoordinate Original y screen cooridnate at start of gesture + * @param zoom Zoom level to be registered + */ + public static void trackGestureEvent(@NonNull Projection projection, @NonNull String gestureId, float xCoordinate, float yCoordinate, double zoom) { + LatLng tapLatLng = projection.fromScreenLocation(new PointF(xCoordinate, yCoordinate)); + + // NaN and Infinite checks to prevent JSON errors at send to server time + if (Double.isNaN(tapLatLng.getLatitude()) || Double.isNaN(tapLatLng.getLongitude())) { + Timber.d("trackGestureEvent() has a NaN lat or lon. Returning."); + return; + } + + if (Double.isInfinite(tapLatLng.getLatitude()) || Double.isInfinite(tapLatLng.getLongitude())) { + Timber.d("trackGestureEvent() has an Infinite lat or lon. Returning."); + return; + } + + Hashtable<String, Object> evt = new Hashtable<>(); + evt.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_MAP_CLICK); + evt.put(MapboxEvent.ATTRIBUTE_CREATED, MapboxEventManager.generateCreateDate()); + evt.put(MapboxEvent.KEY_GESTURE_ID, gestureId); + evt.put(MapboxEvent.KEY_LATITUDE, tapLatLng.getLatitude()); + evt.put(MapboxEvent.KEY_LONGITUDE, tapLatLng.getLongitude()); + evt.put(MapboxEvent.KEY_ZOOM, zoom); + + MapboxEventManager.getMapboxEventManager().pushEvent(evt); + } + + /** + * Helper method for tracking DragEnd gesture event + * See {@see MapboxEvent#TYPE_MAP_DRAGEND} + * + * @param projection projection of the Map object. + * @param xCoordinate Original x screen coordinate at end of drag + * @param yCoordinate Orginal y screen coordinate at end of drag + * @param zoom Zoom level to be registered + */ + public static void trackGestureDragEndEvent(@NonNull Projection projection, float xCoordinate, float yCoordinate, double zoom) { + LatLng tapLatLng = projection.fromScreenLocation(new PointF(xCoordinate, yCoordinate)); + + // NaN and Infinite checks to prevent JSON errors at send to server time + if (Double.isNaN(tapLatLng.getLatitude()) || Double.isNaN(tapLatLng.getLongitude())) { + Timber.d("trackGestureDragEndEvent() has a NaN lat or lon. Returning."); + return; + } + + if (Double.isInfinite(tapLatLng.getLatitude()) || Double.isInfinite(tapLatLng.getLongitude())) { + Timber.d("trackGestureDragEndEvent() has an Infinite lat or lon. Returning."); + return; + } + + Hashtable<String, Object> evt = new Hashtable<>(); + evt.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_MAP_DRAGEND); + evt.put(MapboxEvent.ATTRIBUTE_CREATED, MapboxEventManager.generateCreateDate()); + evt.put(MapboxEvent.KEY_LATITUDE, tapLatLng.getLatitude()); + evt.put(MapboxEvent.KEY_LONGITUDE, tapLatLng.getLongitude()); + evt.put(MapboxEvent.KEY_ZOOM, zoom); + + MapboxEventManager.getMapboxEventManager().pushEvent(evt); + } } |