summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeith Bade <leith@mapbox.com>2015-10-20 22:37:50 +1100
committerLeith Bade <leith@mapbox.com>2015-10-22 17:07:10 +1100
commit8b79a8d6e63c6ae927dc32770ffeadd58be023e7 (patch)
treeb4e588269f7a92b51f247e650409adfd17ec49e7
parent729a793b0eb55f3016bff54cf7fe22039b10b76e (diff)
downloadqtlocation-mapboxgl-8b79a8d6e63c6ae927dc32770ffeadd58be023e7.tar.gz
[android] Implement UserLocationView
Draws animated and synced GPS marker. Inlcudes direction arrow and accuracy ring. Fade out the compass after a delay when reset to north. Lots of other performance and memory improvements. add OnMyLocationChangeListener Move FAB with snackbar Fix getMetersPerPixelAtLatitude Fix some incorrect annotations Fixes #2668 Fixes #2411 Fixes #2678 Fixes #2675 Fixes #2566 Fixes #2549 Fixes #2692 Fixes #2572
-rw-r--r--android/cpp/jni.cpp2
-rw-r--r--android/cpp/native_map_view.cpp33
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java17
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java35
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java12
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java96
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java299
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java32
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java462
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.pngbin4788 -> 0 bytes
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.pngbin4046 -> 0 bytes
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.pngbin4379 -> 0 bytes
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.pngbin5720 -> 0 bytes
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.pngbin7175 -> 0 bytes
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.pngbin8809 -> 0 bytes
-rw-r--r--android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/InfoWindowAdapterActivity.java3
-rw-r--r--android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MainActivity.java37
-rw-r--r--android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml44
-rw-r--r--include/mbgl/android/native_map_view.hpp5
19 files changed, 796 insertions, 281 deletions
diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp
index bf16a3dbfb..c242f94e38 100644
--- a/android/cpp/jni.cpp
+++ b/android/cpp/jni.cpp
@@ -481,7 +481,7 @@ void JNICALL nativeRenderSync(JNIEnv *env, jobject obj, jlong nativeMapViewPtr)
mbgl::Log::Debug(mbgl::Event::JNI, "nativeRenderSync");
assert(nativeMapViewPtr != 0);
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
- nativeMapView->renderSync();
+ nativeMapView->getMap().renderSync();
}
void JNICALL nativeViewResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint width, jint height) {
diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp
index 31fb5453a3..9c4d4b1d10 100644
--- a/android/cpp/native_map_view.cpp
+++ b/android/cpp/native_map_view.cpp
@@ -131,12 +131,22 @@ std::array<uint16_t, 2> NativeMapView::getFramebufferSize() const {
void NativeMapView::activate() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::activate");
+
+ assert(vm != nullptr);
+
+ renderDetach = attach_jni_thread(vm, &renderEnv, "Map Thread");
+
if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE) && (context != EGL_NO_CONTEXT)) {
if (!eglMakeCurrent(display, surface, surface, context)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d",
eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
+
+ if (!eglSwapInterval(display, 0)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglSwapInterval() returned error %d", eglGetError());
+ throw new std::runtime_error("eglSwapInterval() failed");
+ }
} else {
mbgl::Log::Info(mbgl::Event::Android, "Not activating as we are not ready");
}
@@ -144,6 +154,9 @@ void NativeMapView::activate() {
void NativeMapView::deactivate() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::deactivate");
+
+ assert(vm != nullptr);
+
if (display != EGL_NO_DISPLAY) {
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d",
@@ -153,6 +166,8 @@ void NativeMapView::deactivate() {
} else {
mbgl::Log::Info(mbgl::Event::Android, "Not deactivating as we are not ready");
}
+
+ detach_jni_thread(vm, &renderEnv, renderDetach);
}
void NativeMapView::invalidate() {
@@ -173,11 +188,15 @@ void NativeMapView::invalidate() {
}
void NativeMapView::beforeRender() {
+ mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::beforeRender()");
// no-op
}
void NativeMapView::afterRender() {
- mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::afterRender");
+ mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::afterRender()");
+
+ assert(vm != nullptr);
+ assert(obj != nullptr);
if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) {
if (!eglSwapBuffers(display, surface)) {
@@ -185,6 +204,7 @@ void NativeMapView::afterRender() {
eglGetError());
throw new std::runtime_error("eglSwapBuffers() failed");
}
+
updateFps();
} else {
mbgl::Log::Info(mbgl::Event::Android, "Not swapping as we are not ready");
@@ -709,17 +729,6 @@ void NativeMapView::updateFps() {
detach_jni_thread(vm, &env, detach);
}
-void NativeMapView::renderSync() {
- mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::renderSync()");
-
- if (map->isPaused()) {
- mbgl::Log::Debug(mbgl::Event::Android, "Not rendering as map is paused");
- return;
- }
-
- map->renderSync();
-}
-
void NativeMapView::resizeView(int w, int h) {
width = w;
height = h;
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java
index f5f748ad03..1b8f6491ea 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java
@@ -2,6 +2,8 @@ package com.mapbox.mapboxsdk.annotations;
import com.mapbox.mapboxsdk.views.MapView;
+import java.lang.ref.WeakReference;
+
public abstract class Annotation implements Comparable<Annotation> {
/**
@@ -10,7 +12,7 @@ public abstract class Annotation implements Comparable<Annotation> {
* Internal C++ id is stored as unsigned int.
*/
private long id = -1; // -1 unless added to a MapView
- private MapView mapView;
+ private WeakReference<MapView> mapView;
private float alpha = 1.0f;
private boolean visible = true;
@@ -33,8 +35,10 @@ public abstract class Annotation implements Comparable<Annotation> {
}
public void remove() {
- if (mapView == null) return;
- mapView.removeAnnotation(this);
+ if ((mapView == null) || (mapView.get() == null)) {
+ return;
+ }
+ mapView.get().removeAnnotation(this);
}
void setAlpha(float alpha) {
@@ -52,11 +56,14 @@ public abstract class Annotation implements Comparable<Annotation> {
* Do not use this method. Used internally by the SDK.
*/
public void setMapView(MapView mapView) {
- this.mapView = mapView;
+ this.mapView = new WeakReference<MapView>(mapView);
}
protected MapView getMapView() {
- return mapView;
+ if (mapView == null) {
+ return null;
+ }
+ return mapView.get();
}
void setVisible(boolean visible) {
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java
index 0e60c2510b..4b4fdb8c10 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java
@@ -13,13 +13,15 @@ import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.views.MapView;
+import java.lang.ref.WeakReference;
+
/**
* A tooltip view
*/
final class InfoWindow {
- private Marker boundMarker;
- private MapView mMapView;
+ private WeakReference<Marker> mBoundMarker;
+ private WeakReference<MapView> mMapView;
private boolean mIsVisible;
protected View mView;
@@ -29,7 +31,7 @@ final class InfoWindow {
static int mImageId = 0;
public InfoWindow(int layoutResId, MapView mapView) {
- mMapView = mapView;
+ mMapView = new WeakReference<MapView>(mapView);
mIsVisible = false;
mView = LayoutInflater.from(mapView.getContext()).inflate(layoutResId, mapView, false);
@@ -50,7 +52,7 @@ final class InfoWindow {
}
public InfoWindow(View view, MapView mapView) {
- mMapView = mapView;
+ mMapView = new WeakReference<MapView>(mapView);
mIsVisible = false;
mView = view;
@@ -80,7 +82,7 @@ final class InfoWindow {
mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
// Calculate default Android x,y coordinate
- PointF coords = mMapView.toScreenLocation(position);
+ PointF coords = mMapView.get().toScreenLocation(position);
float x = coords.x - (mView.getMeasuredWidth() / 2) + offsetX;
float y = coords.y - mView.getMeasuredHeight() + offsetY;
@@ -89,25 +91,25 @@ final class InfoWindow {
float left = x;
// get right/left map view
- float mapRight = mMapView.getRight();
- float mapLeft = mMapView.getLeft();
+ float mapRight = mMapView.get().getRight();
+ float mapLeft = mMapView.get().getLeft();
if (mView instanceof InfoWindowView) {
// only apply repositioning/margin for InfoWindowView
- Resources resources = mMapView.getContext().getResources();
+ Resources resources = mMapView.get().getContext().getResources();
float margin = resources.getDimension(R.dimen.infowindow_margin);
float tipViewOffset = resources.getDimension(R.dimen.infowindow_tipview_width) / 2;
float tipViewMarginLeft = mView.getMeasuredWidth() / 2 - tipViewOffset;
// fit screen on right
- if (right > mMapView.getRight()) {
+ if (right > mMapView.get().getRight()) {
x -= right - mapRight;
tipViewMarginLeft += right - mapRight + tipViewOffset;
right = x + mView.getMeasuredWidth();
}
// fit screen left
- if (left < mMapView.getLeft()) {
+ if (left < mMapView.get().getLeft()) {
x += mapLeft - left;
tipViewMarginLeft -= mapLeft - left + tipViewOffset;
left = x;
@@ -136,7 +138,7 @@ final class InfoWindow {
mView.setY(y);
close(); //if it was already opened
- mMapView.addView(mView, lp);
+ mMapView.get().addView(mView, lp);
mIsVisible = true;
return this;
}
@@ -172,7 +174,7 @@ final class InfoWindow {
* @return the mapView
*/
public MapView getMapView() {
- return mMapView;
+ return mMapView.get();
}
/**
@@ -204,13 +206,16 @@ final class InfoWindow {
//by default, do nothing
}
- public InfoWindow setBoundMarker(Marker aBoundMarker) {
- this.boundMarker = aBoundMarker;
+ public InfoWindow setBoundMarker(Marker boundMarker) {
+ mBoundMarker = new WeakReference<Marker>(boundMarker);
return this;
}
public Marker getBoundMarker() {
- return boundMarker;
+ if (mBoundMarker == null) {
+ return null;
+ }
+ return mBoundMarker.get();
}
/**
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
index 71480e9588..7f036aca49 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
@@ -71,16 +71,28 @@ public class LatLng implements ILatLng, Parcelable, Serializable {
altitude = in.readDouble();
}
+ public void setLatitude(double latitude) {
+ this.latitude = latitude;
+ }
+
@Override
public double getLatitude() {
return latitude;
}
+ public void setLongitude(double longitude) {
+ this.longitude = longitude;
+ }
+
@Override
public double getLongitude() {
return longitude;
}
+ public void setAltitude(double altitude) {
+ this.altitude = altitude;
+ }
+
@Override
public double getAltitude() {
return altitude;
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java
index e1d0723581..715b577328 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java
@@ -1,23 +1,27 @@
package com.mapbox.mapboxsdk.views;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
-import android.hardware.GeomagneticField;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.location.Location;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
import android.widget.ImageView;
+
import com.mapbox.mapboxsdk.R;
import java.lang.ref.WeakReference;
+import java.util.Timer;
+import java.util.TimerTask;
final class CompassView extends ImageView {
+ private Timer mNorthTimer;
+ private double mDirection = 0.0f;
+ private ViewPropertyAnimator mFadeAnimator;
+
public CompassView(Context context) {
super(context);
initialize(context);
@@ -34,20 +38,94 @@ final class CompassView extends ImageView {
}
private void initialize(Context context) {
+
// View configuration
setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass));
setContentDescription(getResources().getString(R.string.compassContentDescription));
+ setEnabled(false);
// Layout params
float mScreenDensity = context.getResources().getDisplayMetrics().density;
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) (48 * mScreenDensity), (int) (48 * mScreenDensity));
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams((int) (48 * mScreenDensity), (int) (48 * mScreenDensity));
setLayoutParams(lp);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
- setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ if (enabled) {
+ if (mDirection != 0.0) {
+ if (mNorthTimer != null){
+ mNorthTimer.cancel();
+ mNorthTimer = null;
+ }
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ setAlpha(1.0f);
+ setVisibility(View.VISIBLE);
+ }
+ } else {
+ if (mNorthTimer != null){
+ mNorthTimer.cancel();
+ mNorthTimer = null;
+ }
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public void update(double direction) {
+ mDirection = direction;
+ setRotation((float) direction);
+
+ if (direction == 0.0) {
+ if (getVisibility() == View.INVISIBLE) {
+ return;
+ }
+
+ if (mNorthTimer == null) {
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+
+ mNorthTimer = new Timer("CompassView North timer");
+ mNorthTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ setAlpha(1.0f);
+ mFadeAnimator = animate().alpha(0.0f).setDuration(1000);
+ mFadeAnimator.setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setVisibility(View.INVISIBLE);
+ mNorthTimer = null;
+ }
+ });
+ }
+ });
+ }
+ }, 1000);
+ }
+ } else {
+ if (mNorthTimer != null){
+ mNorthTimer.cancel();
+ mNorthTimer = null;
+ }
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ setAlpha(1.0f);
+ setVisibility(View.VISIBLE);
+ }
}
public static class CompassClickListener implements View.OnClickListener {
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java
index dd757082c4..b4e4e7b77e 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java
@@ -1,6 +1,5 @@
package com.mapbox.mapboxsdk.views;
-import android.animation.Animator;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -13,7 +12,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.Matrix;
+import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
@@ -48,7 +47,6 @@ import android.view.TextureView;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -73,10 +71,6 @@ import com.mapbox.mapboxsdk.geometry.BoundingBox;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngZoom;
import com.mapbox.mapboxsdk.utils.ApiAccess;
-import com.mapzen.android.lost.api.LocationListener;
-import com.mapzen.android.lost.api.LocationRequest;
-import com.mapzen.android.lost.api.LocationServices;
-import com.mapzen.android.lost.api.LostApiClient;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -170,7 +164,7 @@ public final class MapView extends FrameLayout {
private NativeMapView mNativeMapView;
// Used to track rendering
- private Boolean mDirty = false;
+ private TextureView mTextureView;
// Used to handle DPI scaling
private float mScreenDensity = 1.0f;
@@ -194,14 +188,7 @@ public final class MapView extends FrameLayout {
private Context mContext;
// Used for user location
- private LostApiClient mLocationClient;
- private LocationRequest mLocationRequest;
- private ImageView mGpsMarker;
- private float mGpsMarkerOffset;
- private Location mGpsLocation;
- private MyLocationListener mLocationListener;
- private ViewPropertyAnimator mGpsMarkerAnimatorX;
- private ViewPropertyAnimator mGpsMarkerAnimatorY;
+ private UserLocationView mUserLocationView;
// Used for the compass
private CompassView mCompassView;
@@ -222,7 +209,7 @@ public final class MapView extends FrameLayout {
private ImageView mAttributionsView;
// Used to manage MapChange event listeners
- private ArrayList<OnMapChangedListener> mOnMapChangedListener;
+ private ArrayList<OnMapChangedListener> mOnMapChangedListener = new ArrayList<>();
// Used to manage map click event listeners
private OnMapClickListener mOnMapClickListener;
@@ -247,7 +234,6 @@ public final class MapView extends FrameLayout {
private boolean mScrollEnabled = true;
private boolean mRotateEnabled = true;
private String mStyleUrl;
- private boolean mIsMyLocationEnabled = false;
//
// Inner classes
@@ -479,10 +465,26 @@ public final class MapView extends FrameLayout {
* @return View to be shown as a {@code InfoWindow}. If null is returned the default
* {@code InfoWindow} will be shown.
*/
- @NonNull
+ @Nullable
View getInfoWindow(@NonNull Marker marker);
}
+ /**
+ * Interface definition for a callback to be invoked when the the My Location dot
+ * (which signifies the user's location) changes location.
+ *
+ * @see MapView#setOnMyLocationChangeListener(OnMyLocationChangeListener)
+ */
+ public interface OnMyLocationChangeListener {
+ /**
+ * Called when the location of the My Location dot has changed
+ * (be it latitude/longitude, bearing or accuracy).
+ *
+ * @param location The current location of the My Location dot The type of map change event.
+ */
+ void onMyLocationChange(@Nullable Location location);
+ }
+
//
// Constructors
//
@@ -563,9 +565,12 @@ public final class MapView extends FrameLayout {
// Save the context
mContext = context;
+ setWillNotDraw(false);
+
// Create the TextureView
- TextureView textureView = new TextureView(mContext);
- addView(textureView);
+ mTextureView = new TextureView(mContext);
+ addView(mTextureView);
+ mTextureView.setSurfaceTextureListener(new SurfaceTextureListener());
// Check if we are in Android Studio UI editor to avoid error in layout preview
if (isInEditMode()) {
@@ -600,9 +605,6 @@ public final class MapView extends FrameLayout {
setFocusableInTouchMode(true);
requestFocus();
- // Register the TextureView callbacks
- textureView.setSurfaceTextureListener(new SurfaceTextureListener());
-
// Touch gesture detectors
mGestureDetector = new GestureDetectorCompat(context, new GestureListener());
mGestureDetector.setIsLongpressEnabled(true);
@@ -628,26 +630,12 @@ public final class MapView extends FrameLayout {
onConnectivityChanged(isConnected);
}
- // Setup location services
- mLocationClient = new LostApiClient.Builder(getContext()).build();
- mLocationRequest = LocationRequest.create()
- .setFastestInterval(1250l)
- .setSmallestDisplacement(2.0f)
- .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
-
// Setup user location UI
- mGpsMarker = new ImageView(getContext());
- mGpsMarker.setImageResource(R.drawable.location_marker);
- mGpsMarker.setVisibility(View.INVISIBLE);
- float iconSize = 27.0f * mScreenDensity;
- mGpsMarkerOffset = iconSize/2;
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) iconSize, (int) iconSize);
- mGpsMarker.setLayoutParams(lp);
- addView(mGpsMarker);
+ mUserLocationView = new UserLocationView(this, getContext());
+ addView(mUserLocationView);
// Setup compass
mCompassView = new CompassView(mContext);
- mCompassView.setVisibility(View.INVISIBLE);
mCompassView.setOnClickListener(new CompassView.CompassClickListener(this));
addView(mCompassView);
@@ -655,7 +643,7 @@ public final class MapView extends FrameLayout {
mLogoView = new ImageView(mContext);
mLogoView.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_logo_mapbox));
mLogoView.setContentDescription(getResources().getString(R.string.mapboxIconContentDescription));
- LayoutParams logoParams = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams logoParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mLogoView.setLayoutParams(logoParams);
addView(mLogoView);
@@ -675,10 +663,6 @@ public final class MapView extends FrameLayout {
addView(mAttributionsView);
mAttributionsView.setOnClickListener(new AttributionOnClickListener(this));
- // Setup Support For Listener Tracking
- // MapView's internal listener is setup in onCreate()
- mOnMapChangedListener = new ArrayList<>();
-
// Load the attributes
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0);
try {
@@ -887,6 +871,9 @@ public final class MapView extends FrameLayout {
public void onDestroy() {
mNativeMapView.terminateContext();
mNativeMapView.terminateDisplay();
+ mNativeMapView.destroySurface();
+ mNativeMapView.destroy();
+ mNativeMapView = null;
}
/**
@@ -901,6 +888,7 @@ public final class MapView extends FrameLayout {
*/
@UiThread
public void onStop() {
+ mUserLocationView.cancelAnimations();
}
/**
@@ -912,10 +900,7 @@ public final class MapView extends FrameLayout {
getContext().unregisterReceiver(mConnectivityReceiver);
mConnectivityReceiver = null;
- if (mIsMyLocationEnabled) {
- toggleGps(false);
- }
-
+ mUserLocationView.pause();
mNativeMapView.pause();
}
@@ -928,11 +913,9 @@ public final class MapView extends FrameLayout {
mConnectivityReceiver = new ConnectivityReceiver();
mContext.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
- if (mIsMyLocationEnabled) {
- toggleGps(true);
- }
-
+ mUserLocationView.resume();
mNativeMapView.resume();
+ mNativeMapView.update();
}
/**
@@ -1360,7 +1343,6 @@ public final class MapView extends FrameLayout {
* @param style The bundled style. Accepts one of the values from {@link Style}.
*/
@UiThread
- @NonNull
public void setStyle(@Style.StyleUrl String style) {
setStyleUrl(style);
}
@@ -1694,8 +1676,10 @@ public final class MapView extends FrameLayout {
throw new NullPointerException("markerOptionsList is null");
}
- List<Marker> markers = new ArrayList<>(markerOptionsList.size());
- for (MarkerOptions markerOptions : markerOptionsList) {
+ int count = markerOptionsList.size();
+ List<Marker> markers = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ MarkerOptions markerOptions = markerOptionsList.get(i);
Marker marker = prepareMarker(markerOptions);
markers.add(marker);
}
@@ -1703,7 +1687,6 @@ public final class MapView extends FrameLayout {
long[] ids = mNativeMapView.addMarkers(markers);
Marker m;
- int count = markers.size();
for (int i = 0; i < count; i++) {
m = markers.get(i);
m.setId(ids[i]);
@@ -1749,8 +1732,10 @@ public final class MapView extends FrameLayout {
}
// TODO make faster in JNI
- List<Polyline> polylines = new ArrayList<>(polylineOptionsList.size());
- for (PolylineOptions polylineOptions : polylineOptionsList) {
+ int count = polylineOptionsList.size();
+ List<Polyline> polylines = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ PolylineOptions polylineOptions = polylineOptionsList.get(i);
polylines.add(addPolyline(polylineOptions));
}
@@ -1842,7 +1827,9 @@ public final class MapView extends FrameLayout {
}
// TODO make faster in JNI
- for (Annotation annotation : annotationList) {
+ int count = annotationList.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = annotationList.get(i);
removeAnnotation(annotation);
}
}
@@ -1887,8 +1874,8 @@ public final class MapView extends FrameLayout {
long[] ids = mNativeMapView.getAnnotationsInBounds(bbox);
List<Long> idsList = new ArrayList<>(ids.length);
- for (long id : ids) {
- idsList.add(id);
+ for (int i = 0; i < ids.length; i++) {
+ idsList.add(ids[i]);
}
List<Marker> annotations = new ArrayList<>(ids.length);
@@ -1924,7 +1911,7 @@ public final class MapView extends FrameLayout {
*/
@UiThread
public double getMetersPerPixelAtLatitude(@FloatRange(from = -180, to = 180) double latitude) {
- return mNativeMapView.getMetersPerPixelAtLatitude(latitude, getZoomLevel());
+ return mNativeMapView.getMetersPerPixelAtLatitude(latitude, getZoomLevel()) / mScreenDensity;
}
private void selectMarker(Marker marker) {
@@ -1977,7 +1964,9 @@ public final class MapView extends FrameLayout {
}
private void adjustTopOffsetPixels() {
- for (Annotation annotation : mAnnotations) {
+ int count = mAnnotations.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
if (annotation instanceof Marker) {
Marker marker = (Marker) annotation;
marker.setTopOffsetPixels(
@@ -2012,29 +2001,16 @@ public final class MapView extends FrameLayout {
// Called when the map needs to be rerendered
// Called via JNI from NativeMapView
- synchronized protected void onInvalidate() {
- if (!mDirty) {
- mDirty = true;
- postRender();
- }
+ protected void onInvalidate() {
+ postInvalidate();
}
- private void postRender() {
- Runnable mRunnable = new Runnable() {
- @Override
- public void run() {
- updateCompass();
- updateGpsMarker();
- mNativeMapView.renderSync();
- mDirty = false;
- }
- };
-
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- postOnAnimation(mRunnable);
- } else {
- postDelayed(mRunnable, 1000 / 60);
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (!mNativeMapView.isPaused()) {
+ mNativeMapView.renderSync();
}
+ super.onDraw(canvas);
}
@Override
@@ -2059,7 +2035,9 @@ public final class MapView extends FrameLayout {
// Must do all EGL/GL ES destruction here
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- mNativeMapView.destroySurface();
+ if (mNativeMapView != null) {
+ mNativeMapView.destroySurface();
+ }
return true;
}
@@ -2070,13 +2048,20 @@ public final class MapView extends FrameLayout {
mNativeMapView.resizeFramebuffer(width, height);
}
- // Not used
+ // Called when the SurfaceTexure frame is drawn to screen
+ // Must sync with UI here
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- // Do nothing
+ mCompassView.update(getDirection());
+ mUserLocationView.update();
}
}
+ // Used by UserLocationView
+ void update() {
+ mNativeMapView.update();
+ }
+
//
// View events
//
@@ -2111,7 +2096,7 @@ public final class MapView extends FrameLayout {
// Called when user touches the screen, all positions are absolute
@Override
- public boolean onTouchEvent(MotionEvent event) {
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
// Check and ignore non touch or left clicks
if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
@@ -2260,7 +2245,9 @@ public final class MapView extends FrameLayout {
if (newSelectedMarkerId >= 0) {
- for (Annotation annotation : mAnnotations) {
+ int count = mAnnotations.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
if (annotation instanceof Marker) {
if (annotation.getId() == newSelectedMarkerId) {
if (mSelectedMarker == null || annotation.getId() != mSelectedMarker.getId()) {
@@ -2512,7 +2499,7 @@ public final class MapView extends FrameLayout {
// Called when the user presses a key, also called for repeating keys held
// down
@Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
+ public 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;
@@ -2761,7 +2748,7 @@ public final class MapView extends FrameLayout {
// Called when the mouse pointer enters or exits the view
// or when it fades in or out due to movement
@Override
- public boolean onHoverEvent(MotionEvent event) {
+ public boolean onHoverEvent(@NonNull MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
@@ -2840,18 +2827,12 @@ public final class MapView extends FrameLayout {
// Called when the map view transformation has changed
// Called via JNI from NativeMapView
// Forward to any listeners
- protected void onMapChanged(int rawChange) {
- final int mapChange = rawChange;
+ protected void onMapChanged(int mapChange) {
if (mOnMapChangedListener != null) {
- post(new Runnable() {
- @Override
- public void run() {
- for (OnMapChangedListener listener : mOnMapChangedListener) {
- //noinspection ResourceType
- listener.onMapChanged(mapChange);
- }
- }
- });
+ int count = mOnMapChangedListener.size();
+ for (int i = 0; i < count; i++) {
+ mOnMapChangedListener.get(i).onMapChanged(mapChange);
+ }
}
}
@@ -2883,14 +2864,14 @@ public final class MapView extends FrameLayout {
// Called via JNI from NativeMapView
// Forward to any listener
protected void onFpsChanged(final double fps) {
- if (mOnFpsChangedListener != null) {
- post(new Runnable() {
- @Override
- public void run() {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnFpsChangedListener != null) {
mOnFpsChangedListener.onFpsChanged(fps);
}
- });
- }
+ }
+ });
}
/**
@@ -2959,13 +2940,13 @@ public final class MapView extends FrameLayout {
*/
@UiThread
public boolean isMyLocationEnabled() {
- return mIsMyLocationEnabled;
+ return mUserLocationView.isEnabled();
}
/**
* Enables or disables the my-location layer.
* While enabled, the my-location layer continuously draws an indication of a user's current
- * location.
+ * location and bearing.
* <p/>
* In order to use the my-location-layer feature you need to request permission for either
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
@@ -2975,8 +2956,7 @@ public final class MapView extends FrameLayout {
*/
@UiThread
public void setMyLocationEnabled(boolean enabled) {
- mIsMyLocationEnabled = enabled;
- toggleGps(enabled);
+ mUserLocationView.setEnabled(enabled);
}
/**
@@ -2987,74 +2967,19 @@ public final class MapView extends FrameLayout {
@UiThread
@Nullable
public Location getMyLocation() {
- return mGpsLocation;
+ return mUserLocationView.getLocation();
}
/**
- * Enabled / Disable GPS location updates along with updating the UI
+ * Sets a callback that's invoked when the the My Location dot
+ * (which signifies the user's location) changes location.
*
- * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled
+ * @param listener The callback that's invoked when the user clicks on a marker.
+ * To unset the callback, use null.
*/
- private void toggleGps(boolean enableGps) {
- if (enableGps) {
- if (!mLocationClient.isConnected()) {
- mGpsLocation = null;
- mLocationClient.connect();
- updateLocation(LocationServices.FusedLocationApi.getLastLocation());
- mLocationListener = new MyLocationListener();
- LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, mLocationListener);
- }
- } else {
- if (mLocationClient.isConnected()) {
- LocationServices.FusedLocationApi.removeLocationUpdates(mLocationListener);
- mLocationListener = null;
- mLocationClient.disconnect();
- mGpsLocation = null;
- }
- }
-
- onInvalidate();
- }
-
- private class MyLocationListener implements LocationListener {
- @Override
- public void onLocationChanged(Location location) {
- updateLocation(location);
- }
- }
-
- // Handles location updates from GPS
- private void updateLocation(Location location) {
- if (location != null) {
- mGpsLocation = location;
- updateGpsMarker();
- }
- }
-
- private void updateGpsMarker() {
- if (mIsMyLocationEnabled && mGpsLocation != null) {
- mGpsMarker.setVisibility(View.VISIBLE);
- LatLng coordinate = new LatLng(mGpsLocation);
- PointF screenLocation = toScreenLocation(coordinate);
- if (!mDirty) {
- // Map is idle, animate change of location
- mGpsMarkerAnimatorX = mGpsMarker.animate().x(screenLocation.x - mGpsMarkerOffset);
- mGpsMarkerAnimatorY = mGpsMarker.animate().y(screenLocation.y - mGpsMarkerOffset);
- } else {
- // Map is not idle, set value, don't animate
- if (mGpsMarkerAnimatorX != null) {
- mGpsMarkerAnimatorX.cancel();
- mGpsMarkerAnimatorY.cancel();
- }
- // Reposition correctly
- mGpsMarker.setX(screenLocation.x - mGpsMarkerOffset);
- mGpsMarker.setY(screenLocation.y - mGpsMarkerOffset);
- }
- } else {
- if (mGpsMarker != null) {
- mGpsMarker.setVisibility(View.INVISIBLE);
- }
- }
+ @UiThread
+ public void setOnMyLocationChangeListener(@Nullable OnMyLocationChangeListener listener) {
+ mUserLocationView.setOnMyLocationChangeListener(listener);
}
//
@@ -3084,7 +3009,6 @@ public final class MapView extends FrameLayout {
@UiThread
public void setCompassEnabled(boolean compassEnabled) {
mCompassView.setEnabled(compassEnabled);
- onInvalidate();
}
/**
@@ -3096,8 +3020,8 @@ public final class MapView extends FrameLayout {
* @param gravity One of the values from {@link Gravity}.
* @see Gravity
*/
- @UiThread
- public void setCompassGravity(int gravity) {
+ @UiThread
+ public void setCompassGravity(int gravity) {
setWidgetGravity(mCompassView, gravity);
}
@@ -3115,25 +3039,6 @@ public final class MapView extends FrameLayout {
setWidgetMargins(mCompassView, left, top, right, bottom);
}
- // Rotates an ImageView - does not work if the ImageView has padding, use margins
- private void rotateImageView(ImageView imageView, float angle) {
- Matrix matrix = new Matrix();
- matrix.setScale((float) imageView.getWidth() / (float) imageView.getDrawable().getIntrinsicWidth(), (float) imageView.getHeight() / (float) imageView.getDrawable().getIntrinsicHeight());
- matrix.postRotate(angle, (float) imageView.getWidth() / 2.0f, (float) imageView.getHeight() / 2.0f);
- imageView.setImageMatrix(matrix);
- imageView.setScaleType(ImageView.ScaleType.MATRIX);
- }
-
- // Updates the UI to match the current map's position
- private void updateCompass() {
- if (isCompassEnabled()) {
- mCompassView.setVisibility(VISIBLE);
- rotateImageView(mCompassView, (float) getDirection());
- } else {
- mCompassView.setVisibility(INVISIBLE);
- }
- }
-
//
// Logo
//
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java
index 29542f35f3..50b242a9f4 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java
@@ -10,6 +10,8 @@ import com.mapbox.mapboxsdk.geometry.BoundingBox;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngZoom;
import com.mapbox.mapboxsdk.geometry.ProjectedMeters;
+
+import java.lang.ref.WeakReference;
import java.util.List;
// Class that wraps the native methods for convenience
@@ -23,11 +25,13 @@ final class NativeMapView {
// Instance members
//
+ boolean mDestroyed = false;
+
// Holds the pointer to JNI NativeMapView
private long mNativeMapViewPtr = 0;
// Used for callbacks
- private MapView mMapView;
+ private WeakReference<MapView> mMapView;
//
// Static methods
@@ -50,7 +54,7 @@ final class NativeMapView {
throw new IllegalArgumentException("totalMemory cannot be negative.");
}
- mMapView = mapView;
+ mMapView = new WeakReference<>(mapView);
// Create the NativeMapView
mNativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath, pixelRatio, availableProcessors, totalMemory);
@@ -60,6 +64,17 @@ final class NativeMapView {
// Methods
//
+ public void destroy() {
+ nativeDestroy(mNativeMapViewPtr);
+ mNativeMapViewPtr = 0;
+ mMapView = null;
+ mDestroyed = true;
+ }
+
+ public boolean wasDestroyed() {
+ return mDestroyed;
+ }
+
public void initializeDisplay() {
nativeInitializeDisplay(mNativeMapViewPtr);
}
@@ -434,28 +449,21 @@ final class NativeMapView {
//
protected void onInvalidate() {
- mMapView.onInvalidate();
+ mMapView.get().onInvalidate();
}
protected void onMapChanged(int rawChange) {
- mMapView.onMapChanged(rawChange);
+ mMapView.get().onMapChanged(rawChange);
}
protected void onFpsChanged(double fps) {
- mMapView.onFpsChanged(fps);
+ mMapView.get().onFpsChanged(fps);
}
//
// JNI methods
//
- @Override
- protected void finalize() throws Throwable {
- nativeDestroy(mNativeMapViewPtr);
- mNativeMapViewPtr = 0;
- super.finalize();
- }
-
private native long nativeCreate(String cachePath, String dataPath, String apkPath, float pixelRatio, int availableProcessors, long totalMemory);
private native void nativeDestroy(long nativeMapViewPtr);
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
new file mode 100644
index 0000000000..3d2416ecd4
--- /dev/null
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
@@ -0,0 +1,462 @@
+package com.mapbox.mapboxsdk.views;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.location.Location;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapzen.android.lost.api.LocationListener;
+import com.mapzen.android.lost.api.LocationRequest;
+import com.mapzen.android.lost.api.LocationServices;
+import com.mapzen.android.lost.api.LostApiClient;
+
+import java.lang.ref.WeakReference;
+
+final class UserLocationView extends View {
+
+ private WeakReference<MapView> mMapView;
+
+ private float mDensity;
+ private PointF mMarkerScreenPoint;
+ private Matrix mMarkerScreenMatrix;
+
+ private Paint mOuterRingPaint;
+ private Path mOuterRingPath;
+ private RectF mOuterRingBounds;
+
+ private Paint mInnerRingPaintFill;
+ private Paint mInnerRingPaintStroke;
+ private Path mInnerRingPath;
+
+ private boolean mShowDirection;
+ private Paint mDirectionArrowPaint;
+ private Path mDirectionArrowPath;
+
+ private boolean mShowAccuracy;
+ private Paint mAccuracyPaintFill;
+ private Paint mAccuracyPaintStroke;
+ private Path mAccuracyPath;
+ private RectF mAccuracyBounds;
+
+ private Rect mDirtyRect;
+ private RectF mDirtyRectF;
+
+ private boolean mShowMarker;
+ private LatLng mMarkerCoordinate;
+ private ValueAnimator mMarkerCoordinateAnimator;
+ private float mMarkerDirection;
+ private ObjectAnimator mMarkerDirectionAnimator;
+ private float mMarkerAccuracy;
+ private ObjectAnimator mMarkerAccuracyAnimator;
+
+ private boolean mPaused = false;
+ private LostApiClient mLocationClient;
+ private LocationRequest mLocationRequest;
+ private Location mUserLocation;
+ private MyLocationListener mLocationListener;
+
+ MapView.OnMyLocationChangeListener mOnMyLocationChangeListener;
+
+ public UserLocationView(MapView mapView, Context context) {
+ super(context);
+ initialize(mapView, context);
+ }
+
+ public UserLocationView(MapView mapView, Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(mapView, context);
+ }
+
+ public UserLocationView(MapView mapView, Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(mapView, context);
+ }
+
+ private void initialize(MapView mapView, Context context) {
+ mMapView = new WeakReference<MapView>(mapView);
+
+ // View configuration
+ setEnabled(false);
+
+ // Layout params
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ setLayoutParams(lp);
+
+ // Setup location services
+ mLocationClient = new LostApiClient.Builder(getContext()).build();
+ mLocationRequest = LocationRequest.create()
+ .setFastestInterval(1000)
+ .setSmallestDisplacement(3.0f)
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+
+ // Setup the custom paint
+ Resources resources = context.getResources();
+ mDensity = resources.getDisplayMetrics().density;
+ mMarkerScreenPoint = new PointF();
+ mMarkerScreenMatrix = new Matrix();
+
+ mOuterRingPaint = new Paint();
+ mOuterRingPaint.setAntiAlias(true);
+ mOuterRingPaint.setStyle(Paint.Style.FILL);
+ mOuterRingPaint.setColor(Color.BLACK);
+ mOuterRingPaint.setAlpha((int) (255 * 0.5f));
+
+ mOuterRingPath = new Path();
+ mOuterRingPath.addCircle(0.0f, 0.0f, 15.0f * mDensity, Path.Direction.CW);
+
+ mOuterRingBounds = new RectF();
+ mOuterRingPath.computeBounds(mOuterRingBounds, false);
+
+ mInnerRingPaintFill = new Paint();
+ mInnerRingPaintFill.setAntiAlias(true);
+ mInnerRingPaintFill.setStyle(Paint.Style.FILL);
+ mInnerRingPaintFill.setColor(Color.WHITE);
+
+ mInnerRingPaintStroke = new Paint();
+ mInnerRingPaintStroke.setAntiAlias(true);
+ mInnerRingPaintStroke.setStyle(Paint.Style.STROKE);
+ mInnerRingPaintStroke.setStrokeWidth(1.0f * mDensity);
+ mInnerRingPaintStroke.setColor(Color.BLACK);
+
+ mInnerRingPath = new Path();
+ mInnerRingPath.addCircle(0.0f, 0.0f, 7.5f * mDensity, Path.Direction.CW);
+
+ mDirectionArrowPaint = mInnerRingPaintFill;
+
+ mDirectionArrowPath = new Path();
+ mDirectionArrowPath.moveTo(0.0f, -15.0f * mDensity);
+ mDirectionArrowPath.arcTo(
+ new RectF(
+ -9.0f * mDensity, -9.0f * mDensity,
+ 9.0f * mDensity, 9.0f * mDensity),
+ 225.0f, 90.0f, false);
+ mDirectionArrowPath.close();
+
+ mAccuracyPaintFill = new Paint();
+ mAccuracyPaintFill.setAntiAlias(true);
+ mAccuracyPaintFill.setStyle(Paint.Style.FILL);
+ mAccuracyPaintFill.setColor(Color.BLACK);
+ mAccuracyPaintFill.setAlpha((int) (255 * 0.25f));
+
+ mAccuracyPaintStroke = new Paint();
+ mAccuracyPaintStroke.setAntiAlias(true);
+ mAccuracyPaintStroke.setStyle(Paint.Style.STROKE);
+ mAccuracyPaintStroke.setStrokeWidth(0.5f * mDensity);
+ mAccuracyPaintStroke.setColor(Color.BLACK);
+ mAccuracyPaintStroke.setAlpha((int) (255 * 0.5f));
+
+ mAccuracyPath = new Path();
+ mAccuracyBounds = new RectF();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (!mShowMarker) {
+ return;
+ }
+
+ canvas.concat(mMarkerScreenMatrix);
+
+ if (!canvas.quickReject(mOuterRingPath, Canvas.EdgeType.AA)) {
+ if (mShowAccuracy) {
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintFill);
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintStroke);
+ }
+ canvas.drawPath(mOuterRingPath, mOuterRingPaint);
+ canvas.drawPath(mInnerRingPath, mInnerRingPaintFill);
+ canvas.drawPath(mInnerRingPath, mInnerRingPaintStroke);
+ if (mShowDirection) {
+ canvas.drawPath(mDirectionArrowPath, mDirectionArrowPaint);
+ }
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ toggleGps(enabled);
+ }
+
+ public void update() {
+ if (isEnabled() && mShowMarker) {
+ setVisibility(View.VISIBLE);
+
+ // compute new marker position
+ // TODO add JNI method that takes existing pointf
+ mMarkerScreenPoint = mMapView.get().toScreenLocation(mMarkerCoordinate);
+ mMarkerScreenMatrix.reset();
+ mMarkerScreenMatrix.setTranslate(
+ mMarkerScreenPoint.x,
+ mMarkerScreenPoint.y);
+
+ // rotate so arrow in points to bearing
+ if (mShowDirection) {
+ mMarkerScreenMatrix.preRotate(mMarkerDirection +
+ (float) mMapView.get().getDirection());
+ }
+
+ // adjust accuracy circle
+ if (mShowAccuracy) {
+ mAccuracyPath.reset();
+ mAccuracyPath.addCircle(0.0f, 0.0f,
+ (float) (mMarkerAccuracy / mMapView.get().getMetersPerPixelAtLatitude(
+ mMarkerCoordinate.getLatitude())),
+ Path.Direction.CW);
+
+ mAccuracyPath.computeBounds(mAccuracyBounds, false);
+ }
+
+ // invalidate changed pixels
+ if (mDirtyRect == null) {
+ mDirtyRect = new Rect();
+ mDirtyRectF = new RectF();
+ } else {
+ // the old marker location
+ // TODO need to take into account accuracy stroke
+ //invalidate(mDirtyRect);
+ invalidate();
+ }
+
+ RectF largerBounds;
+ if (mAccuracyBounds.contains(mOuterRingBounds)) {
+ largerBounds = mAccuracyBounds;
+ } else {
+ largerBounds = mOuterRingBounds;
+ }
+ mMarkerScreenMatrix.mapRect(mDirtyRectF, largerBounds);
+ mDirtyRectF.roundOut(mDirtyRect);
+ // TODO need to take into account accuracy stroke
+ //invalidate(mDirtyRect); // the new marker location
+ invalidate();
+ } else {
+ setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public Location getLocation() {
+ return mUserLocation;
+ }
+
+ /**
+ * Enabled / Disable GPS location updates along with updating the UI
+ *
+ * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled
+ */
+ private void toggleGps(boolean enableGps) {
+ if (mLocationClient == null) {
+ return;
+ }
+
+ if (enableGps) {
+ if (!mLocationClient.isConnected()) {
+ mUserLocation = null;
+ mLocationClient.connect();
+ setLocation(LocationServices.FusedLocationApi.getLastLocation());
+ mLocationListener = new MyLocationListener();
+ LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, mLocationListener);
+ }
+ } else {
+ if (mLocationClient.isConnected()) {
+ LocationServices.FusedLocationApi.removeLocationUpdates(mLocationListener);
+ mLocationListener = null;
+ mLocationClient.disconnect();
+ mUserLocation = null;
+ }
+ }
+ }
+
+ private class MyLocationListener implements LocationListener {
+ @Override
+ public void onLocationChanged(Location location) {
+ if (mPaused) {
+ return;
+ }
+ setLocation(location);
+ }
+ }
+
+ // Handles location updates from GPS
+ private void setLocation(Location location) {
+ // if null we should hide the marker
+ if (location == null) {
+ mShowMarker = false;
+ mShowDirection = false;
+ mShowAccuracy = false;
+
+ cancelAnimations();
+
+ mUserLocation = null;
+ mMarkerCoordinate = null;
+ return;
+ }
+
+ if (mMarkerCoordinateAnimator != null) {
+ mMarkerCoordinateAnimator.end();
+ mMarkerCoordinateAnimator = null;
+ }
+
+ if (mMarkerDirectionAnimator != null) {
+ mMarkerDirectionAnimator.end();
+ mMarkerDirectionAnimator = null;
+ }
+
+ if (mMarkerAccuracyAnimator != null) {
+ mMarkerAccuracyAnimator.end();
+ mMarkerAccuracyAnimator = null;
+ }
+
+ mShowMarker = true;
+
+ LatLng previousCoordinate;
+ if (mUserLocation == null) {
+ previousCoordinate = new LatLng(location);
+ } else {
+ previousCoordinate = new LatLng(mUserLocation);
+ }
+
+ mMarkerCoordinateAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mMarkerCoordinateAnimator.setDuration(1000);
+ mMarkerCoordinateAnimator.addUpdateListener(new MarkerCoordinateAnimatorListener(
+ previousCoordinate, new LatLng(location)
+ ));
+ mMarkerCoordinateAnimator.start();
+
+ mShowDirection = location.hasBearing();
+ if (mShowDirection) {
+ if (mUserLocation != null && mUserLocation.hasBearing()) {
+ mMarkerDirection = mUserLocation.getBearing();
+ }
+ float oldDir = mMarkerDirection;
+ float newDir = location.getBearing();
+ float diff = oldDir - newDir;
+ if (diff > 180.0f) {
+ newDir += 360.0f;
+ } else if (diff < -180.0f) {
+ newDir -= 360.f;
+ }
+ mMarkerDirectionAnimator = ObjectAnimator.ofFloat(this, "direction", oldDir, newDir);
+ mMarkerDirectionAnimator.setDuration(1000);
+ mMarkerDirectionAnimator.start();
+ }
+
+ mShowAccuracy = location.hasAccuracy();
+ if (mShowAccuracy) {
+ if (mUserLocation != null && mUserLocation.hasAccuracy()) {
+ mMarkerAccuracy = mUserLocation.getAccuracy();
+ }
+ mMarkerAccuracyAnimator = ObjectAnimator.ofFloat(this, "accuracy", location.getAccuracy());
+ mMarkerAccuracyAnimator.setDuration(1000);
+ mMarkerAccuracyAnimator.start();
+ }
+
+ mUserLocation = location;
+ updateOnNextFrame();
+
+ if (mOnMyLocationChangeListener != null) {
+ mOnMyLocationChangeListener.onMyLocationChange(location);
+ }
+ }
+
+ void updateOnNextFrame() {
+ mMapView.get().update();
+ }
+
+ public void pause() {
+ mPaused = true;
+ toggleGps(false);
+ }
+
+ public void resume() {
+ mPaused = false;
+ if (isEnabled()) {
+ toggleGps(true);
+ }
+ }
+
+ public void setOnMyLocationChangeListener(@Nullable MapView.OnMyLocationChangeListener listener) {
+ mOnMyLocationChangeListener = listener;
+ }
+
+ // public for animator only
+ public float getDirection() {
+ return mMarkerDirection;
+ }
+
+ // public for animator only
+ public void setDirection(float direction) {
+ mMarkerDirection = direction % 360.0f;
+ updateOnNextFrame();
+ }
+
+ // public for animator only
+ public float getAccuracy() {
+ return mMarkerAccuracy;
+ }
+
+ // public for animator only
+ public void setAccuracy(float accuracy) {
+ mMarkerAccuracy = accuracy;
+ updateOnNextFrame();
+ }
+
+ private class MarkerCoordinateAnimatorListener implements ValueAnimator.AnimatorUpdateListener {
+
+ private double mFromLat;
+ private double mFromLng;
+ private double mToLat;
+ private double mToLng;
+
+ private MarkerCoordinateAnimatorListener(LatLng from, LatLng to) {
+ mFromLat = from.getLatitude();
+ mFromLng = from.getLongitude();
+ mToLat = to.getLatitude();
+ mToLng = to.getLongitude();
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float frac = animation.getAnimatedFraction();
+ double latitude = mFromLat + (mToLat - mFromLat) * frac;
+ double longitude = mFromLng + (mToLng - mFromLng) * frac;
+ mMarkerCoordinate.setLatitude(latitude);
+ mMarkerCoordinate.setLongitude(longitude);
+ updateOnNextFrame();
+ }
+ }
+
+ public void cancelAnimations() {
+ if (mMarkerCoordinateAnimator != null) {
+ mMarkerCoordinateAnimator.cancel();
+ mMarkerCoordinateAnimator = null;
+ }
+
+ if (mMarkerDirectionAnimator != null) {
+ mMarkerDirectionAnimator.cancel();
+ mMarkerDirectionAnimator = null;
+ }
+
+ if (mMarkerAccuracyAnimator != null) {
+ mMarkerAccuracyAnimator.cancel();
+ mMarkerAccuracyAnimator = null;
+ }
+ }
+}
diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.png
deleted file mode 100644
index f3a57845b7..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.png
+++ /dev/null
Binary files differ
diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.png
deleted file mode 100644
index 94a7837817..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.png
+++ /dev/null
Binary files differ
diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.png
deleted file mode 100644
index d8c1563880..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.png
+++ /dev/null
Binary files differ
diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.png
deleted file mode 100644
index 157c48adea..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.png
+++ /dev/null
Binary files differ
diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.png
deleted file mode 100644
index ccc56f6601..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.png
+++ /dev/null
Binary files differ
diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.png
deleted file mode 100644
index 2318f1b7cc..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.png
+++ /dev/null
Binary files differ
diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/InfoWindowAdapterActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/InfoWindowAdapterActivity.java
index a5e08f6cd3..62292811d5 100644
--- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/InfoWindowAdapterActivity.java
+++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/InfoWindowAdapterActivity.java
@@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.testapp;
import android.graphics.Color;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
@@ -42,7 +43,7 @@ public class InfoWindowAdapterActivity extends AppCompatActivity {
private int tenDp = (int) getResources().getDimension(R.dimen.attr_margin);
@Override
- public View getInfoWindow(Marker marker) {
+ public View getInfoWindow(@NonNull Marker marker) {
TextView textView = new TextView(InfoWindowAdapterActivity.this);
textView.setText(marker.getTitle());
textView.setTextColor(Color.WHITE);
diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MainActivity.java
index 019d4c763d..361e6fd443 100644
--- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MainActivity.java
+++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MainActivity.java
@@ -7,6 +7,8 @@ import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar;
@@ -63,6 +65,7 @@ public class MainActivity extends AppCompatActivity {
private TextView mFpsTextView;
private int mSelectedStyle = R.id.actionStyleMapboxStreets;
private NavigationView mNavigationView;
+ private CoordinatorLayout mCoordinatorLayout;
// Used for GPS
private FloatingActionButton mLocationFAB;
@@ -102,6 +105,8 @@ public class MainActivity extends AppCompatActivity {
setupDrawerContent(mNavigationView);
}
+ mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout);
+
mMapView = (MapView) findViewById(R.id.mainMapView);
mMapView.setAccessToken(ApiAccess.getToken(this));
mMapView.onCreate(savedInstanceState);
@@ -110,7 +115,7 @@ public class MainActivity extends AppCompatActivity {
mMapView.setOnMapLongClickListener(new MapView.OnMapLongClickListener() {
@Override
- public void onMapLongClick(LatLng point) {
+ public void onMapLongClick(@NonNull LatLng point) {
mMapView.addMarker(new MarkerOptions()
.position(point)
.title("Dropped Pin")
@@ -122,21 +127,41 @@ public class MainActivity extends AppCompatActivity {
mMapView.setOnMapClickListener(new MapView.OnMapClickListener() {
@Override
- public void onMapClick(LatLng point) {
+ public void onMapClick(@NonNull LatLng point) {
String location = latLngFormatter.format(point.getLatitude()) + ", " +
latLngFormatter.format(point.getLongitude());
- Snackbar.make(findViewById(android.R.id.content), "Map Click Listener " + location, Snackbar.LENGTH_SHORT).show();
+ Snackbar.make(mCoordinatorLayout, "Map Click Listener " + location, Snackbar.LENGTH_SHORT).show();
}
});
mMapView.setOnMarkerClickListener(new MapView.OnMarkerClickListener() {
@Override
- public boolean onMarkerClick(Marker marker) {
- Snackbar.make(findViewById(android.R.id.content), "Custom Marker Click Listener", Snackbar.LENGTH_SHORT).show();
+ public boolean onMarkerClick(@NonNull Marker marker) {
+ Snackbar.make(mCoordinatorLayout, "Custom Marker Click Listener", Snackbar.LENGTH_SHORT).show();
return false;
}
});
+ mMapView.setOnMyLocationChangeListener(new MapView.OnMyLocationChangeListener() {
+ @Override
+ public void onMyLocationChange(@Nullable Location location) {
+ String desc = "Loc Chg: ";
+ boolean noInfo = true;
+ if (location.hasSpeed()) {
+ desc += String.format("Spd = %.1f km/h ", location.getSpeed() * 3.6f);
+ noInfo = false;
+ }
+ if (location.hasAltitude()) {
+ desc += String.format("Alt = %.0f m ", location.getAltitude());
+ noInfo = false;
+ }
+ if (noInfo) {
+ desc += "No extra info";
+ }
+ Snackbar.make(mCoordinatorLayout, desc, Snackbar.LENGTH_SHORT).show();
+ }
+ });
+
mFpsTextView = (TextView) findViewById(R.id.view_fps);
mFpsTextView.setText("");
@@ -149,7 +174,7 @@ public class MainActivity extends AppCompatActivity {
if (mMapView.isMyLocationEnabled()) {
Location location = mMapView.getMyLocation();
if (location != null) {
- mMapView.setZoomLevel(8);
+ mMapView.setZoomLevel(18);
mMapView.setCenterCoordinate(new LatLng(location));
}
}
diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml
index d62d6423e5..b97fb4fcae 100644
--- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml
+++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml
@@ -27,33 +27,35 @@
android:layout_below="@+id/toolbar">
<android.support.design.widget.CoordinatorLayout
+ android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.mapbox.mapboxsdk.views.MapView
- android:id="@+id/mainMapView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
+ <com.mapbox.mapboxsdk.views.MapView
+ android:id="@+id/mainMapView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
+ <TextView
+ android:id="@+id/view_fps"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:text="@string/label_fps"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
- <TextView
- android:id="@+id/view_fps"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="10dp"
- android:text="@string/label_fps"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/locationFAB"
+ android:src="@drawable/ic_my_location_black_24dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|bottom"
+ android:layout_margin="@dimen/fab_margin"
+ app:backgroundTint="@color/white"
+ />
- <android.support.design.widget.FloatingActionButton
- android:id="@+id/locationFAB"
- android:src="@drawable/ic_my_location_black_24dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end|bottom"
- android:layout_margin="@dimen/fab_margin"
- app:backgroundTint="@color/white"
- />
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
diff --git a/include/mbgl/android/native_map_view.hpp b/include/mbgl/android/native_map_view.hpp
index 0908f75630..7b5473d48c 100644
--- a/include/mbgl/android/native_map_view.hpp
+++ b/include/mbgl/android/native_map_view.hpp
@@ -50,8 +50,6 @@ public:
void enableFps(bool enable);
void updateFps();
- void renderSync();
-
void resizeView(int width, int height);
void resizeFramebuffer(int width, int height);
@@ -89,6 +87,9 @@ private:
int availableProcessors = 0;
size_t totalMemory = 0;
+ jboolean renderDetach = false;
+ JNIEnv *renderEnv = nullptr;
+
// Ensure these are initialised last
std::shared_ptr<mbgl::SQLiteCache> fileCache;
std::unique_ptr<mbgl::DefaultFileSource> fileSource;