diff options
author | Leith Bade <leith@mapbox.com> | 2015-10-20 22:37:50 +1100 |
---|---|---|
committer | Leith Bade <leith@mapbox.com> | 2015-10-22 17:07:10 +1100 |
commit | 8b79a8d6e63c6ae927dc32770ffeadd58be023e7 (patch) | |
tree | b4e588269f7a92b51f247e650409adfd17ec49e7 | |
parent | 729a793b0eb55f3016bff54cf7fe22039b10b76e (diff) | |
download | qtlocation-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
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 Binary files differdeleted file mode 100644 index f3a57845b7..0000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 94a7837817..0000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index d8c1563880..0000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 157c48adea..0000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index ccc56f6601..0000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 2318f1b7cc..0000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.png +++ /dev/null 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; |