com.mapbox.mapboxsdk.views.MapView Maven / Gradle / Ivy
package com.mapbox.mapboxsdk.views;
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.Fragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.FloatRange;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresPermission;
import android.support.annotation.UiThread;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ScaleGestureDetectorCompat;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ZoomButtonsController;
import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector;
import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.annotations.Annotation;
import com.mapbox.mapboxsdk.annotations.Icon;
import com.mapbox.mapboxsdk.annotations.InfoWindow;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
import com.mapbox.mapboxsdk.annotations.Polygon;
import com.mapbox.mapboxsdk.annotations.PolygonOptions;
import com.mapbox.mapboxsdk.annotations.Polyline;
import com.mapbox.mapboxsdk.annotations.PolylineOptions;
import com.mapbox.mapboxsdk.annotations.IconFactory;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdate;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.constants.MathConstants;
import com.mapbox.mapboxsdk.constants.MyBearingTracking;
import com.mapbox.mapboxsdk.constants.MyLocationTracking;
import com.mapbox.mapboxsdk.constants.Style;
import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException;
import com.mapbox.mapboxsdk.exceptions.InvalidAccessTokenException;
import com.mapbox.mapboxsdk.geometry.BoundingBox;
import com.mapbox.mapboxsdk.geometry.CoordinateBounds;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngZoom;
import com.mapbox.mapboxsdk.layers.CustomLayer;
import com.mapbox.mapboxsdk.utils.ApiAccess;
import com.mapbox.mapboxsdk.utils.MathUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
*
* A {@code MapView} provides an embeddable map interface.
* You use this class to display map information and to manipulate the map contents from your application.
* You can center the map on a given coordinate, specify the size of the area you want to display,
* and style the features of the map to fit your application's use case.
*
*
* Use of {@code MapView} requires a Mapbox API access token.
* Obtain an access token on the Mapbox account page.
*
* Warning: Please note that you are responsible for getting permission to use the map data,
* and for ensuring your use adheres to the relevant terms of use.
*
* @see MapView#setAccessToken(String)
*/
public final class MapView extends FrameLayout {
//
// Static members
//
// Used for logging
private static final String TAG = "MapView";
// Used for animation
private static final long ANIMATION_DURATION = 300;
// Used for saving instance state
private static final String STATE_CENTER_LATLNG = "centerLatLng";
private static final String STATE_CENTER_DIRECTION = "centerDirection";
private static final String STATE_ZOOM = "zoomLevel";
private static final String STATE_TILT = "tilt";
private static final String STATE_ZOOM_ENABLED = "zoomEnabled";
private static final String STATE_SCROLL_ENABLED = "scrollEnabled";
private static final String STATE_ROTATE_ENABLED = "rotateEnabled";
private static final String STATE_TILT_ENABLED = "tiltEnabled";
private static final String STATE_ZOOM_CONTROLS_ENABLED = "zoomControlsEnabled";
private static final String STATE_DEBUG_ACTIVE = "debugActive";
private static final String STATE_STYLE_URL = "styleUrl";
private static final String STATE_ACCESS_TOKEN = "accessToken";
private static final String STATE_STYLE_CLASSES = "styleClasses";
private static final String STATE_DEFAULT_TRANSITION_DURATION = "defaultTransitionDuration";
private static final String STATE_MY_LOCATION_ENABLED = "myLocationEnabled";
private static final String STATE_MY_LOCATION_TRACKING_MODE = "myLocationTracking";
private static final String STATE_MY_BEARING_TRACKING_MODE = "myBearingTracking";
private static final String STATE_COMPASS_ENABLED = "compassEnabled";
private static final String STATE_COMPASS_GRAVITY = "compassGravity";
private static final String STATE_COMPASS_MARGIN_LEFT = "compassMarginLeft";
private static final String STATE_COMPASS_MARGIN_TOP = "compassMarginTop";
private static final String STATE_COMPASS_MARGIN_RIGHT = "compassMarginRight";
private static final String STATE_COMPASS_MARGIN_BOTTOM = "compassMarginBottom";
private static final String STATE_LOGO_GRAVITY = "logoGravity";
private static final String STATE_LOGO_MARGIN_LEFT = "logoMarginLeft";
private static final String STATE_LOGO_MARGIN_TOP = "logoMarginTop";
private static final String STATE_LOGO_MARGIN_RIGHT = "logoMarginRight";
private static final String STATE_LOGO_MARGIN_BOTTOM = "logoMarginBottom";
private static final String STATE_LOGO_VISIBILITY = "logoVisibility";
private static final String STATE_ATTRIBUTION_GRAVITY = "attrGravity";
private static final String STATE_ATTRIBUTION_MARGIN_LEFT = "attrMarginLeft";
private static final String STATE_ATTRIBUTION_MARGIN_TOP = "attrMarginTop";
private static final String STATE_ATTRIBUTION_MARGIN_RIGHT = "attrMarginRight";
private static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "atrrMarginBottom";
private static final String STATE_ATTRIBUTION_VISIBILITY = "atrrVisibility";
// Used for positioning views
private static final float DIMENSION_SEVEN_DP = 7f;
private static final float DIMENSION_TEN_DP = 10f;
private static final float DIMENSION_SIXTEEN_DP = 16f;
private static final float DIMENSION_SEVENTYSIX_DP = 76f;
// Used to select "Improve this map" link in the attribution dialog
// Index into R.arrays.attribution_links
private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2;
/**
* The currently supported maximum zoom level.
*
* @see MapView#setZoomLevel(double)
* @deprecated use #MAXIMUM_ZOOM instead.
*/
public static final double MAXIMUM_ZOOM_LEVEL = 18.0;
/**
* The currently supported maximum zoom level.
*
* @see MapView#setZoom(double)
*/
public static final double MAXIMUM_ZOOM = 18.0;
/**
* The currently supported maximum and minimum tilt values.
*
* @see MapView#setTilt(Double, Long)
*/
private static final double MINIMUM_TILT = 0;
private static final double MAXIMUM_TILT = 60;
//
// Instance members
//
// Used to call JNI NativeMapView
private NativeMapView mNativeMapView;
// Used to track rendering
private TextureView mTextureView;
// Used to handle DPI scaling
private float mScreenDensity = 1.0f;
// Touch gesture detectors
private GestureDetectorCompat mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private RotateGestureDetector mRotateGestureDetector;
private ShoveGestureDetector mShoveGestureDetector;
private boolean mTwoTap = false;
private boolean mZoomStarted = false;
private boolean mQuickZoom = false;
// Shows zoom buttons
private ZoomButtonsController mZoomButtonsController;
private boolean mZoomControlsEnabled = false;
// Used to track trackball long presses
private TrackballLongPressTimeOut mCurrentTrackballLongPressTimeOut;
// Receives changes to network connectivity
private ConnectivityReceiver mConnectivityReceiver;
// Used for user location
private UserLocationView mUserLocationView;
// Used for the compass
private CompassView mCompassView;
// Used for displaying annotations
// Every annotation that has been added to the map
private final List mAnnotations = new ArrayList<>();
private List mMarkersNearLastTap = new ArrayList<>();
private List mSelectedMarkers = new ArrayList<>();
private List mInfoWindows = new ArrayList<>();
private InfoWindowAdapter mInfoWindowAdapter;
private List mIcons = new ArrayList<>();
// Used for the Mapbox Logo
private ImageView mLogoView;
// Used for attributions control
private ImageView mAttributionsView;
// Used to manage MapChange event listeners
private List mOnMapChangedListener = new ArrayList<>();
// Used to manage map click event listeners
private OnMapClickListener mOnMapClickListener;
private OnMapLongClickListener mOnMapLongClickListener;
// Used to manage fling and scroll event listeners
private OnFlingListener mOnFlingListener;
private OnScrollListener mOnScrollListener;
// Used to manage marker click event listeners
private OnMarkerClickListener mOnMarkerClickListener;
private OnInfoWindowClickListener mOnInfoWindowClickListener;
// Used to manage FPS change event listeners
private OnFpsChangedListener mOnFpsChangedListener;
// Used to manage tracking mode changes
private OnMyLocationTrackingModeChangeListener mOnMyLocationTrackingModeChangeListener;
private OnMyBearingTrackingModeChangeListener mOnMyBearingTrackingModeChangeListener;
//
// Properties
//
// These are properties with setters/getters, saved in onSaveInstanceState and XML attributes
private boolean mZoomEnabled = true;
private boolean mScrollEnabled = true;
private boolean mRotateEnabled = true;
private boolean mTiltEnabled = true;
private boolean mAllowConcurrentMultipleOpenInfoWindows = false;
private String mStyleUrl;
//
// Inner classes
//
//
// Enums
//
/**
* Map change event types.
*
* @see MapView.OnMapChangedListener#onMapChanged(int)
*/
@IntDef({REGION_WILL_CHANGE,
REGION_WILL_CHANGE_ANIMATED,
REGION_IS_CHANGING,
REGION_DID_CHANGE,
REGION_DID_CHANGE_ANIMATED,
WILL_START_LOADING_MAP,
DID_FINISH_LOADING_MAP,
DID_FAIL_LOADING_MAP,
WILL_START_RENDERING_FRAME,
DID_FINISH_RENDERING_FRAME,
DID_FINISH_RENDERING_FRAME_FULLY_RENDERED,
WILL_START_RENDERING_MAP,
DID_FINISH_RENDERING_MAP,
DID_FINISH_RENDERING_MAP_FULLY_RENDERED
})
@Retention(RetentionPolicy.SOURCE)
public @interface MapChange {
}
/**
*
* This {@link MapChange} is triggered whenever the currently displayed map region is about to changing
* without an animation.
*
*
* This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends
* with {@link MapView#REGION_DID_CHANGE}.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int REGION_WILL_CHANGE = 0;
/**
*
* This {@link MapChange} is triggered whenever the currently displayed map region is about to changing
* with an animation.
*
*
* This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends
* with {@link MapView#REGION_DID_CHANGE_ANIMATED}.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int REGION_WILL_CHANGE_ANIMATED = 1;
/**
*
* This {@link MapChange} is triggered whenever the currently displayed map region is changing.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int REGION_IS_CHANGING = 2;
/**
*
* This {@link MapChange} is triggered whenever the currently displayed map region finished changing
* without an animation.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int REGION_DID_CHANGE = 3;
/**
*
* This {@link MapChange} is triggered whenever the currently displayed map region finished changing
* with an animation.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int REGION_DID_CHANGE_ANIMATED = 4;
/**
*
* This {@link MapChange} is triggered when the map is about to start loading a new map style.
*
*
* This event is followed by {@link MapView#DID_FINISH_LOADING_MAP} or
* {@link MapView#DID_FAIL_LOADING_MAP}.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int WILL_START_LOADING_MAP = 5;
/**
*
* This {@link MapChange} is triggered when the map has successfully loaded a new map style.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int DID_FINISH_LOADING_MAP = 6;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* This event is triggered when the map has failed to load a new map style.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int DID_FAIL_LOADING_MAP = 7;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int WILL_START_RENDERING_FRAME = 8;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int DID_FINISH_RENDERING_FRAME = 9;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int DID_FINISH_RENDERING_FRAME_FULLY_RENDERED = 10;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int WILL_START_RENDERING_MAP = 11;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int DID_FINISH_RENDERING_MAP = 12;
/**
*
* This {@link MapChange} is currently not implemented.
*
*
* @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
*/
public static final int DID_FINISH_RENDERING_MAP_FULLY_RENDERED = 13;
//
// Interfaces
//
/**
* Interface definition for a callback to be invoked when the map is flinged.
*
* @see MapView#setOnFlingListener(OnFlingListener)
*/
public interface OnFlingListener {
/**
* Called when the map is flinged.
*/
void onFling();
}
/**
* Interface definition for a callback to be invoked when the map is scrolled.
*
* @see MapView#setOnScrollListener(OnScrollListener)
*/
public interface OnScrollListener {
/**
* Called when the map is scrolled.
*/
void onScroll();
}
/**
* Interface definition for a callback to be invoked on every frame rendered to the map view.
*
* @see MapView#setOnFpsChangedListener(OnFpsChangedListener)
*/
public interface OnFpsChangedListener {
/**
* Called for every frame rendered to the map view.
*
* @param fps The average number of frames rendered over the last second.
*/
void onFpsChanged(double fps);
}
/**
* Interface definition for a callback to be invoked when the user clicks on the map view.
*
* @see MapView#setOnMapClickListener(OnMapClickListener)
*/
public interface OnMapClickListener {
/**
* Called when the user clicks on the map view.
*
* @param point The projected map coordinate the user clicked on.
*/
void onMapClick(@NonNull LatLng point);
}
/**
* Interface definition for a callback to be invoked when the user long clicks on the map view.
*
* @see MapView#setOnMapLongClickListener(OnMapLongClickListener)
*/
public interface OnMapLongClickListener {
/**
* Called when the user long clicks on the map view.
*
* @param point The projected map coordinate the user long clicked on.
*/
void onMapLongClick(@NonNull LatLng point);
}
/**
* Interface definition for a callback to be invoked when the user clicks on a marker.
*
* @see MapView#setOnMarkerClickListener(OnMarkerClickListener)
*/
public interface OnMarkerClickListener {
/**
* Called when the user clicks on a marker.
*
* @param marker The marker the user clicked on.
* @return If true the listener has consumed the event and the info window will not be shown.
*/
boolean onMarkerClick(@NonNull Marker marker);
}
/**
* Interface definition for a callback to be invoked when the user clicks on an info window.
*
* @see MapView#setOnInfoWindowClickListener(OnInfoWindowClickListener)
*/
public interface OnInfoWindowClickListener {
/**
* Called when the user clicks on an info window.
*
* @param marker The marker of the info window the user clicked on.
* @return If true the listener has consumed the event and the info window will not be closed.
*/
boolean onMarkerClick(@NonNull Marker marker);
}
/**
* Interface definition for a callback to be invoked when the displayed map view changes.
*
* @see MapView#addOnMapChangedListener(OnMapChangedListener)
* @see MapView.MapChange
*/
public interface OnMapChangedListener {
/**
* Called when the displayed map view changes.
*
* @param change Type of map change event, one of {@link #REGION_WILL_CHANGE},
* {@link #REGION_WILL_CHANGE_ANIMATED},
* {@link #REGION_IS_CHANGING},
* {@link #REGION_DID_CHANGE},
* {@link #REGION_DID_CHANGE_ANIMATED},
* {@link #WILL_START_LOADING_MAP},
* {@link #DID_FAIL_LOADING_MAP},
* {@link #DID_FINISH_LOADING_MAP},
* {@link #WILL_START_RENDERING_FRAME},
* {@link #DID_FINISH_RENDERING_FRAME},
* {@link #DID_FINISH_RENDERING_FRAME_FULLY_RENDERED},
* {@link #WILL_START_RENDERING_MAP},
* {@link #DID_FINISH_RENDERING_MAP},
* {@link #DID_FINISH_RENDERING_MAP_FULLY_RENDERED}.
*/
void onMapChanged(@MapChange int change);
}
/**
* Interface definition for a callback to be invoked when an info window will be shown.
*
* @see MapView#setInfoWindowAdapter(InfoWindowAdapter)
*/
public interface InfoWindowAdapter {
/**
* Called when an info window will be shown as a result of a marker click.
*
* @param marker The marker the user clicked on.
* @return View to be shown as a info window. If null is returned the default
* info window will be shown.
*/
@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);
}
/**
* Interface definition for a callback to be invoked when the the My Location tracking mode changes.
*
* @see MapView#setMyLocationTrackingMode(int)
*/
public interface OnMyLocationTrackingModeChangeListener {
/**
* Called when the tracking mode of My Location tracking has changed
*
* @param myLocationTrackingMode the current active location tracking mode
*/
void onMyLocationTrackingModeChange(@MyLocationTracking.Mode int myLocationTrackingMode);
}
/**
* Interface definition for a callback to be invoked when the the My Location tracking mode changes.
*
* @see MapView#setMyLocationTrackingMode(int)
*/
public interface OnMyBearingTrackingModeChangeListener {
/**
* Called when the tracking mode of My Bearing tracking has changed
*
* @param myBearingTrackingMode the current active bearing tracking mode
*/
void onMyBearingTrackingModeChange(@MyBearingTracking.Mode int myBearingTrackingMode);
}
/**
* A callback interface for reporting when a task is complete or cancelled.
*/
public interface CancelableCallback {
/**
* Invoked when a task is cancelled.
*/
void onCancel();
/**
* Invoked when a task is complete.
*/
void onFinish();
}
//
// Constructors
//
/**
* Simple constructor to use when creating a {@link MapView} from code using the default map style.
*
* @param context The {@link Context} of the {@link android.app.Activity}
* or {@link android.app.Fragment} the {@link MapView} is running in.
* @param accessToken Your public Mapbox access token. Used to load map styles and tiles.
*/
@UiThread
public MapView(@NonNull Context context, @NonNull String accessToken) {
super(context);
if (accessToken == null) {
Log.w(TAG, "accessToken was null, so just returning");
return;
}
initialize(context, null);
setAccessToken(accessToken);
setStyleUrl(null);
}
/**
* Simple constructor to use when creating a {@link MapView} from code using the provided map style URL.
*
* @param context The {@link Context} of the {@link android.app.Activity}
* or {@link android.app.Fragment} the {@link MapView} is running in.
* @param accessToken Your public Mapbox access token. Used to load map styles and tiles.
* @param styleUrl A URL to the map style initially displayed. See {@link MapView#setStyleUrl(String)} for possible values.
* @see MapView#setStyleUrl(String)
*/
@UiThread
public MapView(@NonNull Context context, @NonNull String accessToken, @NonNull String styleUrl) {
super(context);
if (accessToken == null) {
Log.w(TAG, "accessToken was null, so just returning");
return;
}
if (styleUrl == null) {
Log.w(TAG, "styleUrl was null, so just returning");
return;
}
initialize(context, null);
setAccessToken(accessToken);
setStyleUrl(styleUrl);
}
// Constructor that is called when inflating a view from XML.
/**
* Do not call from code.
*/
@UiThread
public MapView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
// Constructor that is called when inflating a view from XML.
/**
* Do not call from code.
*/
@UiThread
public MapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
//
// Initialization
//
// Common initialization code goes here
private void initialize(Context context, AttributeSet attrs) {
if (context == null) {
Log.w(TAG, "context was null, so just returning");
return;
}
// Inflate content
View view = LayoutInflater.from(context).inflate(R.layout.mapview_internal, this);
if (!isInEditMode()) {
setWillNotDraw(false);
}
// Reference the TextureView
mTextureView = (TextureView) view.findViewById(R.id.textureView);
mTextureView.setSurfaceTextureListener(new SurfaceTextureListener());
// Check if we are in Android Studio UI editor to avoid error in layout preview
if (isInEditMode()) {
return;
}
// Get the screen's density
mScreenDensity = context.getResources().getDisplayMetrics().density;
// Get the cache path
String cachePath = context.getCacheDir().getAbsolutePath();
String dataPath = context.getFilesDir().getAbsolutePath();
String apkPath = context.getPackageCodePath();
// Create the NativeMapView
int availableProcessors = Runtime.getRuntime().availableProcessors();
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
ActivityManager activityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.getMemoryInfo(memoryInfo);
long maxMemory = memoryInfo.availMem;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
maxMemory = memoryInfo.totalMem;
}
mNativeMapView = new
NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, maxMemory);
// Ensure this view is interactable
setClickable(true);
setLongClickable(true);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
// Touch gesture detectors
mGestureDetector = new GestureDetectorCompat(context, new GestureListener());
mGestureDetector.setIsLongpressEnabled(true);
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
ScaleGestureDetectorCompat.setQuickScaleEnabled(mScaleGestureDetector, true);
mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
mShoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener());
// Shows the zoom controls
if (!context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) {
mZoomControlsEnabled = true;
}
mZoomButtonsController = new ZoomButtonsController(this);
mZoomButtonsController.setZoomSpeed(ANIMATION_DURATION);
mZoomButtonsController.setOnZoomListener(new OnZoomListener());
// Check current connection status
ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
boolean isConnected = (activeNetwork != null) && activeNetwork.isConnectedOrConnecting();
onConnectivityChanged(isConnected);
// Setup user location UI
mUserLocationView = (UserLocationView) view.findViewById(R.id.userLocationView);
mUserLocationView.setMapView(this);
// Setup compass
mCompassView = (CompassView) view.findViewById(R.id.compassView);
mCompassView.setOnClickListener(new CompassView.CompassClickListener(this));
// Setup Mapbox logo
mLogoView = (ImageView) view.findViewById(R.id.logoView);
// Setup Attributions control
mAttributionsView = (ImageView) view.findViewById(R.id.attributionView);
mAttributionsView.setOnClickListener(new AttributionOnClickListener(this));
// Load the attributes
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0);
try {
double centerLatitude = typedArray.getFloat(R.styleable.MapView_center_latitude, 0.0f);
double centerLongitude = typedArray.getFloat(R.styleable.MapView_center_longitude, 0.0f);
setLatLng(new LatLng(centerLatitude, centerLongitude));
// need to set zoom level first because of limitation on rotating when zoomed out
float zoom = typedArray.getFloat(R.styleable.MapView_zoom, 0.0f);
if(zoom != 0.0f){
setZoom(zoom);
}else{
setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoom_level, 0.0f));
}
setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f));
setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_enabled, true));
setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scroll_enabled, true));
setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotate_enabled, true));
setTiltEnabled(typedArray.getBoolean(R.styleable.MapView_tilt_enabled, true));
setZoomControlsEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_controls_enabled, isZoomControlsEnabled()));
setDebugActive(typedArray.getBoolean(R.styleable.MapView_debug_active, false));
if (typedArray.getString(R.styleable.MapView_style_url) != null) {
setStyleUrl(typedArray.getString(R.styleable.MapView_style_url));
}
if (typedArray.getString(R.styleable.MapView_access_token) != null) {
setAccessToken(typedArray.getString(R.styleable.MapView_access_token));
}
if (typedArray.getString(R.styleable.MapView_style_classes) != null) {
List styleClasses = Arrays.asList(typedArray
.getString(R.styleable.MapView_style_classes).split("\\s*,\\s*"));
for (String styleClass : styleClasses) {
if (styleClass.length() == 0) {
styleClasses.remove(styleClass);
}
}
setStyleClasses(styleClasses);
}
// Compass
setCompassEnabled(typedArray.getBoolean(R.styleable.MapView_compass_enabled, true));
setCompassGravity(typedArray.getInt(R.styleable.MapView_compass_gravity, Gravity.TOP | Gravity.END));
setWidgetMargins(mCompassView, typedArray.getDimension(R.styleable.MapView_compass_margin_left, DIMENSION_TEN_DP)
, typedArray.getDimension(R.styleable.MapView_compass_margin_top, DIMENSION_TEN_DP)
, typedArray.getDimension(R.styleable.MapView_compass_margin_right, DIMENSION_TEN_DP)
, typedArray.getDimension(R.styleable.MapView_compass_margin_bottom, DIMENSION_TEN_DP));
// Logo
setLogoVisibility(typedArray.getInt(R.styleable.MapView_logo_visibility, View.VISIBLE));
setLogoGravity(typedArray.getInt(R.styleable.MapView_logo_gravity, Gravity.BOTTOM | Gravity.START));
setWidgetMargins(mLogoView, typedArray.getDimension(R.styleable.MapView_logo_margin_left, DIMENSION_SIXTEEN_DP)
, typedArray.getDimension(R.styleable.MapView_logo_margin_top, DIMENSION_SIXTEEN_DP)
, typedArray.getDimension(R.styleable.MapView_logo_margin_right, DIMENSION_SIXTEEN_DP)
, typedArray.getDimension(R.styleable.MapView_logo_margin_bottom, DIMENSION_SIXTEEN_DP));
// Attribution
setAttributionVisibility(typedArray.getInt(R.styleable.MapView_attribution_visibility, View.VISIBLE));
setAttributionGravity(typedArray.getInt(R.styleable.MapView_attribution_gravity, Gravity.BOTTOM));
setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attribution_margin_left, DIMENSION_SEVENTYSIX_DP)
, typedArray.getDimension(R.styleable.MapView_attribution_margin_top, DIMENSION_SEVEN_DP)
, typedArray.getDimension(R.styleable.MapView_attribution_margin_right, DIMENSION_SEVEN_DP)
, typedArray.getDimension(R.styleable.MapView_attribution_margin_bottom, DIMENSION_SEVEN_DP));
// User location
try {
//noinspection ResourceType
setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false));
}catch (SecurityException ignore){
// User did not accept location permissions
}
} finally {
typedArray.recycle();
}
}
//
// Lifecycle events
//
/**
*
* You must call this method from the parent's {@link android.app.Activity#onCreate(Bundle)} or
* {@link android.app.Fragment#onCreate(Bundle)}.
*
* You must set a valid access token with {@link MapView#setAccessToken(String)} before you this method
* or an exception will be thrown.
*
* @param savedInstanceState Pass in the parent's savedInstanceState.
* @see MapView#setAccessToken(String)
*/
@UiThread
public void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
setLatLng((LatLng) savedInstanceState.getParcelable(STATE_CENTER_LATLNG));
// need to set zoom level first because of limitation on rotating when zoomed out
setZoom(savedInstanceState.getDouble(STATE_ZOOM));
setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION));
setTilt(savedInstanceState.getDouble(STATE_TILT), null);
setZoomEnabled(savedInstanceState.getBoolean(STATE_ZOOM_ENABLED));
setScrollEnabled(savedInstanceState.getBoolean(STATE_SCROLL_ENABLED));
setRotateEnabled(savedInstanceState.getBoolean(STATE_ROTATE_ENABLED));
setTiltEnabled(savedInstanceState.getBoolean(STATE_TILT_ENABLED));
setZoomControlsEnabled(savedInstanceState.getBoolean(STATE_ZOOM_CONTROLS_ENABLED));
setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE));
setStyleUrl(savedInstanceState.getString(STATE_STYLE_URL));
setAccessToken(savedInstanceState.getString(STATE_ACCESS_TOKEN));
List appliedStyleClasses = savedInstanceState.getStringArrayList(STATE_STYLE_CLASSES);
if (!appliedStyleClasses.isEmpty()) {
setStyleClasses(appliedStyleClasses);
}
mNativeMapView.setDefaultTransitionDuration(
savedInstanceState.getLong(STATE_DEFAULT_TRANSITION_DURATION));
// User location
try {
//noinspection ResourceType
setMyLocationEnabled(savedInstanceState.getBoolean(STATE_MY_LOCATION_ENABLED));
}catch (SecurityException ignore){
// User did not accept location permissions
}
// Compass
setCompassEnabled(savedInstanceState.getBoolean(STATE_COMPASS_ENABLED));
setCompassGravity(savedInstanceState.getInt(STATE_COMPASS_GRAVITY));
setCompassMargins(savedInstanceState.getInt(STATE_COMPASS_MARGIN_LEFT)
, savedInstanceState.getInt(STATE_COMPASS_MARGIN_TOP)
, savedInstanceState.getInt(STATE_COMPASS_MARGIN_RIGHT)
, savedInstanceState.getInt(STATE_COMPASS_MARGIN_BOTTOM));
// Logo
setLogoVisibility(savedInstanceState.getInt(STATE_LOGO_VISIBILITY));
setLogoGravity(savedInstanceState.getInt(STATE_LOGO_GRAVITY));
setLogoMargins(savedInstanceState.getInt(STATE_LOGO_MARGIN_LEFT)
, savedInstanceState.getInt(STATE_LOGO_MARGIN_TOP)
, savedInstanceState.getInt(STATE_LOGO_MARGIN_RIGHT)
, savedInstanceState.getInt(STATE_LOGO_MARGIN_BOTTOM));
// Attribution
setAttributionVisibility(savedInstanceState.getInt(STATE_ATTRIBUTION_VISIBILITY));
setAttributionGravity(savedInstanceState.getInt(STATE_ATTRIBUTION_GRAVITY));
setAttributionMargins(savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_LEFT)
, savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_TOP)
, savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_RIGHT)
, savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_BOTTOM));
//noinspection ResourceType
setMyLocationTrackingMode(savedInstanceState.getInt(STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE));
//noinspection ResourceType
setMyBearingTrackingMode(savedInstanceState.getInt(STATE_MY_BEARING_TRACKING_MODE, MyBearingTracking.NONE));
}
// Force a check for an access token
validateAccessToken(getAccessToken());
// Initialize EGL
mNativeMapView.initializeDisplay();
mNativeMapView.initializeContext();
// Add annotation deselection listener
addOnMapChangedListener(new OnMapChangedListener() {
@Override
public void onMapChanged(@MapChange int change) {
if (change == DID_FINISH_LOADING_MAP) {
reloadIcons();
reloadMarkers();
adjustTopOffsetPixels();
}
}
});
}
/**
* You must call this method from the parent's {@link android.app.Activity#onSaveInstanceState(Bundle)}
* or {@link android.app.Fragment#onSaveInstanceState(Bundle)}.
*
* @param outState Pass in the parent's outState.
*/
@UiThread
public void onSaveInstanceState(@NonNull Bundle outState) {
if (outState == null) {
Log.w(TAG, "outState was null, so just returning");
return;
}
outState.putParcelable(STATE_CENTER_LATLNG, getLatLng());
// need to set zoom level first because of limitation on rotating when zoomed out
outState.putDouble(STATE_ZOOM, getZoom());
outState.putDouble(STATE_CENTER_DIRECTION, getDirection());
outState.putDouble(STATE_TILT, getTilt());
outState.putBoolean(STATE_ZOOM_ENABLED, isZoomEnabled());
outState.putBoolean(STATE_SCROLL_ENABLED, isScrollEnabled());
outState.putBoolean(STATE_ROTATE_ENABLED, isRotateEnabled());
outState.putBoolean(STATE_TILT_ENABLED, isTiltEnabled());
outState.putBoolean(STATE_ZOOM_CONTROLS_ENABLED, isZoomControlsEnabled());
outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive());
outState.putString(STATE_STYLE_URL, getStyleUrl());
outState.putString(STATE_ACCESS_TOKEN, getAccessToken());
outState.putStringArrayList(STATE_STYLE_CLASSES, new ArrayList<>(getStyleClasses()));
outState.putLong(STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration());
outState.putBoolean(STATE_MY_LOCATION_ENABLED, isMyLocationEnabled());
outState.putInt(STATE_MY_LOCATION_TRACKING_MODE, mUserLocationView.getMyLocationTrackingMode());
outState.putInt(STATE_MY_BEARING_TRACKING_MODE, mUserLocationView.getMyBearingTrackingMode());
// Compass
LayoutParams compassParams = (LayoutParams) mCompassView.getLayoutParams();
outState.putBoolean(STATE_COMPASS_ENABLED, isCompassEnabled());
outState.putInt(STATE_COMPASS_GRAVITY, compassParams.gravity);
outState.putInt(STATE_COMPASS_MARGIN_LEFT, compassParams.leftMargin);
outState.putInt(STATE_COMPASS_MARGIN_TOP, compassParams.topMargin);
outState.putInt(STATE_COMPASS_MARGIN_BOTTOM, compassParams.bottomMargin);
outState.putInt(STATE_COMPASS_MARGIN_RIGHT, compassParams.rightMargin);
// Logo
LayoutParams logoParams = (LayoutParams) mLogoView.getLayoutParams();
outState.putInt(STATE_LOGO_GRAVITY, logoParams.gravity);
outState.putInt(STATE_LOGO_MARGIN_LEFT, logoParams.leftMargin);
outState.putInt(STATE_LOGO_MARGIN_TOP, logoParams.topMargin);
outState.putInt(STATE_LOGO_MARGIN_RIGHT, logoParams.rightMargin);
outState.putInt(STATE_LOGO_MARGIN_BOTTOM, logoParams.bottomMargin);
outState.putInt(STATE_LOGO_VISIBILITY, mLogoView.getVisibility());
// Attribution
LayoutParams attrParams = (LayoutParams) mAttributionsView.getLayoutParams();
outState.putInt(STATE_ATTRIBUTION_GRAVITY, attrParams.gravity);
outState.putInt(STATE_ATTRIBUTION_MARGIN_LEFT, attrParams.leftMargin);
outState.putInt(STATE_ATTRIBUTION_MARGIN_TOP, attrParams.topMargin);
outState.putInt(STATE_ATTRIBUTION_MARGIN_RIGHT, attrParams.rightMargin);
outState.putInt(STATE_ATTRIBUTION_MARGIN_BOTTOM, attrParams.bottomMargin);
outState.putInt(STATE_ATTRIBUTION_VISIBILITY, mAttributionsView.getVisibility());
}
/**
* You must call this method from the parent's {@link Activity#onDestroy()} or {@link Fragment#onDestroy()}.
*/
@UiThread
public void onDestroy() {
mNativeMapView.terminateContext();
mNativeMapView.terminateDisplay();
mNativeMapView.destroySurface();
mNativeMapView.destroy();
mNativeMapView = null;
}
/**
* You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()}.
*/
@UiThread
public void onStart() {
mUserLocationView.onStart();
}
/**
* You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()}
*/
@UiThread
public void onStop() {
mUserLocationView.onStop();
}
/**
* You must call this method from the parent's {@link Activity#onPause()} or {@link Fragment#onPause()}.
*/
@UiThread
public void onPause() {
// Register for connectivity changes
getContext().unregisterReceiver(mConnectivityReceiver);
mConnectivityReceiver = null;
mUserLocationView.pause();
mNativeMapView.pause();
}
/**
* You must call this method from the parent's {@link Activity#onResume()} or {@link Fragment#onResume()}.
*/
@UiThread
public void onResume() {
// Register for connectivity changes
mConnectivityReceiver = new ConnectivityReceiver();
getContext().registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
mNativeMapView.resume();
mNativeMapView.update();
mUserLocationView.resume();
}
/**
* You must call this method from the parent's {@link Activity#onLowMemory()} or {@link Fragment#onLowMemory()}.
*/
@UiThread
public void onLowMemory() {
mNativeMapView.onLowMemory();
}
//
// Position
//
/**
* Returns the current {@link LatLng} at the center of the map view.
*
* @return The current center.
*/
@UiThread
@NonNull
public LatLng getLatLng() {
return mNativeMapView.getLatLng();
}
/**
*
* Centers the map on a new {@link LatLng} immediately without changing the zoom level.
*
*
* The initial {@link LatLng} is (0, 0).
*
* If you want to animate the change, use {@link MapView#setLatLng(LatLng, boolean)}.
*
* @param latLng The new center.
* @see MapView#setLatLng(LatLng, boolean)
*/
@UiThread
public void setLatLng(@NonNull LatLng latLng) {
setLatLng(latLng, false);
}
/**
*
* Centers the map on a new {@link LatLng} without changing the zoom level and optionally animates the change.
*
* The initial {@link LatLng} is (0, 0).
*
* @param latLng The new center.
* @param animated If true, animates the change. If false, immediately changes the map.
*/
@UiThread
public void setLatLng(@NonNull LatLng latLng, boolean animated) {
if (latLng == null) {
Log.w(TAG, "latLng was null, so just returning");
return;
}
if (animated) {
CameraPosition cameraPosition = new CameraPosition.Builder(getCameraPosition())
.target(latLng)
.build();
animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition),
(int) ANIMATION_DURATION, null);
} else {
jumpTo(mNativeMapView.getBearing(), latLng, mNativeMapView.getPitch(), mNativeMapView.getZoom());
}
}
/**
*
* Centers the map on a new {@link LatLng} immediately while changing the current zoom level.
*
*
* The initial value is a center {@link LatLng} of (0, 0) and a zoom level of 0.
*
* If you want to animate the change, use {@link MapView#setLatLng(LatLng, boolean)}.
*
* @param latLngZoom The new center and zoom level.
* @see MapView#setLatLng(LatLngZoom, boolean)
*/
@UiThread
public void setLatLng(@NonNull LatLngZoom latLngZoom) {
setLatLng(latLngZoom, false);
}
/**
*
* Centers the map on a new {@link LatLng} while changing the zoom level and optionally animates the change.
*
* The initial value is a center {@link LatLng} of (0, 0) and a zoom level of 0.
*
* @param latLngZoom The new center and zoom level.
* @param animated If true, animates the change. If false, immediately changes the map.
*/
@UiThread
public void setLatLng(@NonNull LatLngZoom latLngZoom, boolean animated) {
if (latLngZoom == null) {
Log.w(TAG, "latLngZoom was null, so just returning");
return;
}
long duration = animated ? ANIMATION_DURATION : 0;
mNativeMapView.cancelTransitions();
mNativeMapView.setLatLngZoom(latLngZoom, duration);
}
/**
* Returns the current coordinate at the center of the map view.
*
* @return The current coordinate.
* @deprecated use {@link #getLatLng()} instead.
*/
@UiThread
@NonNull
@Deprecated
public LatLng getCenterCoordinate() {
return mNativeMapView.getLatLng();
}
/**
*
* Centers the map on a new coordinate immediately without changing the zoom level.
*
*
* The initial coordinate is (0, 0).
*
* If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLng, boolean)}.
*
* @param centerCoordinate The new coordinate.
* @see MapView#setCenterCoordinate(LatLng, boolean)
* @deprecated use {@link #setLatLng(LatLng)}} instead.
*/
@UiThread
@Deprecated
public void setCenterCoordinate(@NonNull LatLng centerCoordinate) {
setCenterCoordinate(centerCoordinate, false);
}
/**
*
* Centers the map on a new coordinate without changing the zoom level and optionally animates the change.
*
* The initial coordinate is (0, 0).
*
* @param centerCoordinate The new coordinate.
* @param animated If true, animates the change. If false, immediately changes the map.
* @deprecated use {@link #setLatLng(LatLng, boolean)}} instead.
*/
@UiThread
@Deprecated
public void setCenterCoordinate(@NonNull LatLng centerCoordinate, boolean animated) {
if (centerCoordinate == null) {
Log.w(TAG, "centerCoordinate was null, so just returning");
return;
}
if (animated) {
CameraPosition cameraPosition = new CameraPosition.Builder(getCameraPosition())
.target(centerCoordinate)
.build();
animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition),
(int) ANIMATION_DURATION, null);
} else {
jumpTo(mNativeMapView.getBearing(), centerCoordinate, mNativeMapView.getPitch(), mNativeMapView.getZoom());
}
}
/**
*
* Centers the map on a new coordinate immediately while changing the current zoom level.
*
*
* The initial value is a center coordinate of (0, 0) and a zoom level of 0.
*
* If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLngZoom, boolean)}.
*
* @param centerCoordinate The new coordinate and zoom level.
* @see MapView#setCenterCoordinate(LatLngZoom, boolean)
* @deprecated use {@link #setLatLng(LatLngZoom)} instead.
*/
@UiThread
@Deprecated
public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate) {
setCenterCoordinate(centerCoordinate, false);
}
/**
*
* Centers the map on a new coordinate while changing the zoom level and optionally animates the change.
*
* The initial value is a center coordinate of (0, 0) and a zoom level of 0.
*
* @param centerCoordinate The new coordinate and zoom level.
* @param animated If true, animates the change. If false, immediately changes the map.
* @deprecated use {@link #setLatLng(LatLngZoom, boolean)}} instead.
*/
@UiThread
@Deprecated
public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate,
boolean animated) {
if (centerCoordinate == null) {
Log.w(TAG, "centerCoordinate was null, so just returning");
return;
}
long duration = animated ? ANIMATION_DURATION : 0;
mNativeMapView.cancelTransitions();
mNativeMapView.setLatLngZoom(centerCoordinate, duration);
}
/**
* Resets the map to the minimum zoom level, a center coordinate of (0, 0), a true north heading,
* and animates the change.
*/
@UiThread
public void resetPosition() {
mNativeMapView.cancelTransitions();
mNativeMapView.resetPosition();
}
/**
* Returns whether the user may scroll around the map.
*
* @return If true, scrolling is enabled.
*/
@UiThread
public boolean isScrollEnabled() {
return mScrollEnabled;
}
/**
*
* Changes whether the user may scroll around the map.
*
*
* This setting controls only user interactions with the map. If you set the value to false,
* you may still change the map location programmatically.
*
* The default value is true.
*
* @param scrollEnabled If true, scrolling is enabled.
*/
@UiThread
public void setScrollEnabled(boolean scrollEnabled) {
this.mScrollEnabled = scrollEnabled;
}
//
// Pitch / Tilt
//
/**
* Gets the current Tilt in degrees of the MapView
*
* @return tilt in degrees
*/
public double getTilt() {
return mNativeMapView.getPitch();
}
/**
* Sets the Tilt in degrees of the MapView.
*
* @param pitch New tilt in degrees
* @param duration Animation time in milliseconds. If null then 0 is used, making the animation immediate.
*/
@FloatRange(from = MINIMUM_TILT, to = MAXIMUM_TILT)
public void setTilt(Double pitch, @Nullable Long duration) {
long actualDuration = 0;
if (duration != null) {
actualDuration = duration;
}
mNativeMapView.setPitch(pitch, actualDuration);
}
//
// Rotation
//
/**
* Returns the current heading of the map relative to true north.
*
* @return The current heading measured in degrees.
*/
@UiThread
@FloatRange(from = 0, to = 360)
public double getDirection() {
double direction = -mNativeMapView.getBearing();
while (direction > 360) {
direction -= 360;
}
while (direction < 0) {
direction += 360;
}
return direction;
}
/**
*
* Rotates the map to a new heading relative to true north immediately.
*
*
* - The value 0 means that the top edge of the map view will correspond to true north.
* - The value 90 means the top of the map will point due east.
* - The value 180 means the top of the map will point due south.
* - The value 270 means the top of the map will point due west.
*
*
* The initial heading is 0.
*
* If you want to animate the change, use {@link MapView#setDirection(double, boolean)}.
*
* @param direction The new heading measured in degrees.
* @see MapView#setDirection(double, boolean)
*/
@UiThread
public void setDirection(@FloatRange(from = 0, to = 360) double direction) {
setDirection(direction, false);
}
/**
*
* Rotates the map to a new heading relative to true north and optionally animates the change.
*
*
* - The value 0 means that the top edge of the map view will correspond to true north.
* - The value 90 means the top of the map will point due east.
* - The value 180 means the top of the map will point due south.
* - The value 270 means the top of the map will point due west.
*
* The initial heading is 0.
*
* @param direction The new heading measured in degrees from true north.
* @param animated If true, animates the change. If false, immediately changes the map.
*/
@UiThread
public void setDirection(@FloatRange(from = 0, to = 360) double direction, boolean animated) {
long duration = animated ? ANIMATION_DURATION : 0;
mNativeMapView.cancelTransitions();
// Out of range direactions are normallised in setBearing
mNativeMapView.setBearing(-direction, duration);
}
/**
* Resets the map heading to true north and animates the change.
*/
@UiThread
public void resetNorth() {
mNativeMapView.cancelTransitions();
mNativeMapView.resetNorth();
}
/**
* Returns whether the user may rotate the map.
*
* @return If true, rotating is enabled.
*/
@UiThread
public boolean isRotateEnabled() {
return mRotateEnabled;
}
/**
*
* Changes whether the user may rotate the map.
*
*
* This setting controls only user interactions with the map. If you set the value to false,
* you may still change the map location programmatically.
*
* The default value is true.
*
* @param rotateEnabled If true, rotating is enabled.
*/
@UiThread
public void setRotateEnabled(boolean rotateEnabled) {
this.mRotateEnabled = rotateEnabled;
}
//
// Scale
//
/**
* Returns the current zoom level of the map view.
*
* @return The current zoom.
*/
@UiThread
@FloatRange(from = 0.0, to = MAXIMUM_ZOOM)
public double getZoom() {
return mNativeMapView.getZoom();
}
/**
*
* Zooms the map to a new zoom level immediately without changing the center coordinate.
*
*
* At zoom level 0, tiles cover the entire world map;
* at zoom level 1, tiles cover 1/14 of the world;
* at zoom level 2, tiles cover 1/16 of the world, and so on.
*
*
* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM}.
*
* If you want to animate the change, use {@link MapView#setZoom(double, boolean)}.
*
* @param zoomLevel The new zoom.
* @see MapView#setZoom(double, boolean)
* @see MapView#MAXIMUM_ZOOM
*/
@UiThread
public void setZoom(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM) double zoomLevel) {
setZoom(zoomLevel, false);
setZoom(zoomLevel, false);
}
/**
*
* Zooms the map to a new zoom level and optionally animates the change without changing the center coordinate.
*
*
* At zoom level 0, tiles cover the entire world map;
* at zoom level 1, tiles cover 1/14 of the world;
* at zoom level 2, tiles cover 1/16 of the world, and so on.
*
* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM}.
*
* @param zoomLevel The new zoom level.
* @param animated If true, animates the change. If false, immediately changes the map.
* @see MapView#MAXIMUM_ZOOM
*/
@UiThread
public void setZoom(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel, boolean animated) {
if ((zoomLevel < 0.0) || (zoomLevel > MAXIMUM_ZOOM_LEVEL)) {
throw new IllegalArgumentException("zoomLevel is < 0 or > MapView.MAXIMUM_ZOOM_LEVEL");
}
long duration = animated ? ANIMATION_DURATION : 0;
mNativeMapView.cancelTransitions();
mNativeMapView.setZoom(zoomLevel, duration);
}
/**
* Returns the current zoom level of the map view.
*
* @return The current zoom level.
* @deprecated use {@link #getZoom()} instead.
*/
@UiThread
@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL)
@Deprecated
public double getZoomLevel() {
return mNativeMapView.getZoom();
}
/**
*
* Zooms the map to a new zoom level immediately without changing the center coordinate.
*
*
* At zoom level 0, tiles cover the entire world map;
* at zoom level 1, tiles cover 1/14 of the world;
* at zoom level 2, tiles cover 1/16 of the world, and so on.
*
*
* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}.
*
* If you want to animate the change, use {@link MapView#setZoomLevel(double, boolean)}.
*
* @param zoomLevel The new coordinate.
* @see MapView#setZoomLevel(double, boolean)
* @see MapView#MAXIMUM_ZOOM_LEVEL
* @deprecated use {@link #setZoom(double)} instead.
*/
@UiThread
@Deprecated
public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel) {
setZoomLevel(zoomLevel, false);
}
/**
*
* Zooms the map to a new zoom level and optionally animates the change without changing the center coordinate.
*
*
* At zoom level 0, tiles cover the entire world map;
* at zoom level 1, tiles cover 1/14 of the world;
* at zoom level 2, tiles cover 1/16 of the world, and so on.
*
* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}.
*
* @param zoomLevel The new coordinate.
* @param animated If true, animates the change. If false, immediately changes the map.
* @see MapView#MAXIMUM_ZOOM_LEVEL
* @deprecated use {@link #setZoom(double, boolean)} instead.
*/
@UiThread
@Deprecated
public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel, boolean animated) {
if ((zoomLevel < 0.0) || (zoomLevel > MAXIMUM_ZOOM_LEVEL)) {
throw new IllegalArgumentException("zoomLevel is < 0 or > MapView.MAXIMUM_ZOOM_LEVEL");
}
long duration = animated ? ANIMATION_DURATION : 0;
mNativeMapView.cancelTransitions();
mNativeMapView.setZoom(zoomLevel, duration);
}
/**
* Returns whether the user may zoom the map.
*
* @return If true, zooming is enabled.
*/
@UiThread
public boolean isZoomEnabled() {
return mZoomEnabled;
}
/**
*
* Changes whether the user may zoom the map.
*
*
* This setting controls only user interactions with the map. If you set the value to false,
* you may still change the map location programmatically.
*
* The default value is true.
*
* @param zoomEnabled If true, zooming is enabled.
*/
@UiThread
public void setZoomEnabled(boolean zoomEnabled) {
this.mZoomEnabled = zoomEnabled;
if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) {
mZoomButtonsController.setVisible(true);
} else {
mZoomButtonsController.setVisible(false);
}
}
/**
* Gets whether the zoom controls are enabled.
*
* @return If true, the zoom controls are enabled.
*/
public boolean isZoomControlsEnabled() {
return mZoomControlsEnabled;
}
/**
*
* Sets whether the zoom controls are enabled.
* If enabled, the zoom controls are a pair of buttons
* (one for zooming in, one for zooming out) that appear on the screen.
* When pressed, they cause the camera to zoom in (or out) by one zoom level.
* If disabled, the zoom controls are not shown.
*
* By default the zoom controls are enabled if the device is only single touch capable;
*
* @param enabled If true, the zoom controls are enabled.
*/
public void setZoomControlsEnabled(boolean enabled) {
mZoomControlsEnabled = enabled;
if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) {
mZoomButtonsController.setVisible(true);
} else {
mZoomButtonsController.setVisible(false);
}
}
// Zoom in or out
private void zoom(boolean zoomIn) {
zoom(zoomIn, -1.0f, -1.0f);
}
private void zoom(boolean zoomIn, float x, float y) {
// Cancel any animation
mNativeMapView.cancelTransitions();
if (zoomIn) {
mNativeMapView.scaleBy(2.0, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION);
} else {
mNativeMapView.scaleBy(0.5, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION);
}
}
//
// Tilt
//
/**
* Returns whether the user may tilt the map.
*
* @return If true, tilting is enabled.
*/
@UiThread
public boolean isTiltEnabled() {
return mTiltEnabled;
}
/**
*
* Changes whether the user may tilt the map.
*
*
* This setting controls only user interactions with the map. If you set the value to false,
* you may still change the map location programmatically.
*
* The default value is true.
*
* @param tiltEnabled If true, tilting is enabled.
*/
@UiThread
public void setTiltEnabled(boolean tiltEnabled) {
this.mTiltEnabled = tiltEnabled;
}
//
// Camera API
//
/**
* Gets the current position of the camera.
* The CameraPosition returned is a snapshot of the current position, and will not automatically update when the camera moves.
*
* @return The current position of the Camera.
*/
public final CameraPosition getCameraPosition() {
return new CameraPosition(getLatLng(), (float) getZoom(), (float) getTilt(), (float) getBearing());
}
/**
* Animates the movement of the camera from the current position to the position defined in the update.
* During the animation, a call to getCameraPosition() returns an intermediate location of the camera.
*
* See CameraUpdateFactory for a set of updates.
*
* @param update The change that should be applied to the camera.
*/
@UiThread
public final void animateCamera(CameraUpdate update) {
animateCamera(update, 1, null);
}
/**
* Animates the movement of the camera from the current position to the position defined in the update and calls an optional callback on completion.
* See CameraUpdateFactory for a set of updates.
* During the animation, a call to getCameraPosition() returns an intermediate location of the camera.
*
* @param update The change that should be applied to the camera.
* @param callback The callback to invoke from the main thread when the animation stops. If the animation completes normally, onFinish() is called; otherwise, onCancel() is called. Do not update or animate the camera from within onCancel().
*/
@UiThread
public final void animateCamera(CameraUpdate update, MapView.CancelableCallback callback) {
animateCamera(update, 1, callback);
}
/**
* Moves the map according to the update with an animation over a specified duration, and calls an optional callback on completion. See CameraUpdateFactory for a set of updates.
* If getCameraPosition() is called during the animation, it will return the current location of the camera in flight.
*
* @param update The change that should be applied to the camera.
* @param durationMs The duration of the animation in milliseconds. This must be strictly positive, otherwise an IllegalArgumentException will be thrown.
* @param callback An optional callback to be notified from the main thread when the animation stops. If the animation stops due to its natural completion, the callback will be notified with onFinish(). If the animation stops due to interruption by a later camera movement or a user gesture, onCancel() will be called. The callback should not attempt to move or animate the camera in its cancellation method. If a callback isn't required, leave it as null.
*/
@UiThread
public final void animateCamera(CameraUpdate update, int durationMs, final MapView.CancelableCallback callback) {
if (update.getTarget() == null) {
Log.w(TAG, "animateCamera with null target coordinate passed in. Will immediately return without animating camera.");
return;
}
mNativeMapView.cancelTransitions();
// Register callbacks early enough
if (callback != null) {
final MapView view = this;
addOnMapChangedListener(new OnMapChangedListener() {
@Override
public void onMapChanged(@MapChange int change) {
if (change == REGION_DID_CHANGE_ANIMATED) {
callback.onFinish();
// Clean up after self
removeOnMapChangedListener(this);
}
}
});
}
// Convert Degrees To Radians
double angle = -1;
if (update.getBearing() >= 0) {
angle = (-update.getBearing()) * MathConstants.DEG2RAD;
}
double pitch = -1;
if (update.getTilt() >= 0) {
double dp = MathUtils.clamp(update.getTilt(), MINIMUM_TILT, MAXIMUM_TILT);
pitch = dp * MathConstants.DEG2RAD;
}
double zoom = -1;
if (update.getZoom() >= 0) {
zoom = update.getZoom();
}
long durationNano = 0;
if (durationMs > 0) {
durationNano = TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS);
}
flyTo(angle, update.getTarget(), durationNano, pitch, zoom);
}
/**
* Ease the map according to the update with an animation over a specified duration, and calls an optional callback on completion. See CameraUpdateFactory for a set of updates.
* If getCameraPosition() is called during the animation, it will return the current location of the camera in flight.
*
* @param update The change that should be applied to the camera.
* @param durationMs The duration of the animation in milliseconds. This must be strictly positive, otherwise an IllegalArgumentException will be thrown.
* @param callback An optional callback to be notified from the main thread when the animation stops. If the animation stops due to its natural completion, the callback will be notified with onFinish(). If the animation stops due to interruption by a later camera movement or a user gesture, onCancel() will be called. The callback should not attempt to move or animate the camera in its cancellation method. If a callback isn't required, leave it as null.
*/
@UiThread
public final void easeCamera(CameraUpdate update, int durationMs, final MapView.CancelableCallback callback) {
if (update.getTarget() == null) {
Log.w(TAG, "easeCamera with null target coordinate passed in. Will immediately return without easing camera.");
return;
}
mNativeMapView.cancelTransitions();
// Register callbacks early enough
if (callback != null) {
final MapView view = this;
addOnMapChangedListener(new OnMapChangedListener() {
@Override
public void onMapChanged(@MapChange int change) {
if (change == REGION_DID_CHANGE_ANIMATED) {
callback.onFinish();
// Clean up after self
removeOnMapChangedListener(this);
}
}
});
}
// Convert Degrees To Radians
double angle = -1;
if (update.getBearing() >= 0) {
angle = (-update.getBearing()) * MathConstants.DEG2RAD;
}
double pitch = -1;
if (update.getTilt() >= 0) {
double dp = MathUtils.clamp(update.getTilt(), MINIMUM_TILT, MAXIMUM_TILT);
pitch = dp * MathConstants.DEG2RAD;
}
double zoom = -1;
if (update.getZoom() >= 0) {
zoom = update.getZoom();
}
long durationNano = 0;
if (durationMs > 0) {
durationNano = TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS);
}
easeTo(angle, update.getTarget(), durationNano, pitch, zoom);
}
/**
* Repositions the camera according to the instructions defined in the update.
* The move is instantaneous, and a subsequent getCameraPosition() will reflect the new position.
* See CameraUpdateFactory for a set of updates.
*
* @param update The change that should be applied to the camera.
*/
@UiThread
public final void moveCamera(CameraUpdate update) {
if (update.getTarget() == null) {
Log.w(TAG, "moveCamera with null target coordinate passed in. Will immediately return without moving camera.");
return;
}
mNativeMapView.cancelTransitions();
// Convert Degrees To Radians
double angle = -1;
if (update.getBearing() >= 0) {
angle = (-update.getBearing()) * MathConstants.DEG2RAD;
}
double pitch = -1;
if (update.getTilt() >= 0) {
double dp = MathUtils.clamp(update.getTilt(), MINIMUM_TILT, MAXIMUM_TILT);
pitch = dp * MathConstants.DEG2RAD;
}
double zoom = -1;
if (update.getZoom() >= 0) {
zoom = update.getZoom();
}
jumpTo(angle, update.getTarget(), pitch, zoom);
}
//
// InfoWindows
//
/**
* Changes whether the map allows concurrent multiple infowindows to be shown.
*
* @param allow If true, map allows concurrent multiple infowindows to be shown.
*/
@UiThread
public void setAllowConcurrentMultipleOpenInfoWindows(boolean allow) {
this.mAllowConcurrentMultipleOpenInfoWindows = allow;
}
/**
* Returns whether the map allows concurrent multiple infowindows to be shown.
*
* @return If true, map allows concurrent multiple infowindows to be shown.
*/
@UiThread
public boolean isAllowConcurrentMultipleOpenInfoWindows() {
return this.mAllowConcurrentMultipleOpenInfoWindows;
}
//
// Debug
//
/**
* Returns whether the map debug information is currently shown.
*
* @return If true, map debug information is currently shown.
*/
@UiThread
public boolean isDebugActive() {
return mNativeMapView.getDebug();
}
/**
*
* Changes whether the map debug information is shown.
*
* The default value is false.
*
* @param debugActive If true, map debug information is shown.
*/
@UiThread
public void setDebugActive(boolean debugActive) {
mNativeMapView.setDebug(debugActive);
}
/**
*
* Cycles through the map debug options.
*
* The value of {@link MapView#isDebugActive()} reflects whether there are
* any map debug options enabled or disabled.
*
* @see MapView#isDebugActive()
*/
@UiThread
public void cycleDebugOptions() {
mNativeMapView.cycleDebugOptions();
}
// True if map has finished loading the view
private boolean isFullyLoaded() {
return mNativeMapView.isFullyLoaded();
}
//
// Styling
//
/**
*
* Loads a new map style from the specified URL.
*
* {@code url} can take the following forms:
*
* - {@code Style.*}: load one of the bundled styles in {@link Style}.
* - {@code mapbox://styles/
/