[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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
Reference in New Issue
Block a user