package com.mapbox.mapboxsdk.views;
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.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.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.Sprite;
import com.mapbox.mapboxsdk.annotations.SpriteFactory;
import com.mapbox.mapboxsdk.constants.MyBearingTracking;
import com.mapbox.mapboxsdk.constants.MyLocationTracking;
import com.mapbox.mapboxsdk.constants.Style;
import com.mapbox.mapboxsdk.exceptions.InvalidAccessTokenException;
import com.mapbox.mapboxsdk.exceptions.SpriteBitmapChangedException;
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.utils.ApiAccess;
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;
/**
* 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_COORDINATE = "centerCoordinate";
private static final String STATE_CENTER_DIRECTION = "centerDirection";
private static final String STATE_ZOOM_LEVEL = "zoomLevel";
private static final String STATE_DIRECTION = "direction";
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_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)
*/
public static final double MAXIMUM_ZOOM_LEVEL = 18.0;
/**
* The currently supported maximum and minimum tilt values.
*
* @see MapView#setTilt(double)
*/
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 SpriteFactory mSpriteFactory;
private ArrayList mSprites = new ArrayList<>();
// Used for the Mapbox Logo
private ImageView mLogoView;
// Used for attributions control
private ImageView mAttributionsView;
// Used to manage MapChange event listeners
private ArrayList 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;
//
// 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}.
*
* More information in {@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}.
*
* More information in {@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.
*
* More information in {@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.
*
* More information in {@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.
*
* More information in {@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}.
*
* More information in {@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.
*
* More information in {@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.
*
* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener}
*/
public static final int DID_FAIL_LOADING_MAP = 7;
/**
* This {@link MapChange} is currently not implemented.
*
* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener}
*/
public static final int WILL_START_RENDERING_FRAME = 8;
/**
* This {@link MapChange} is currently not implemented.
*
* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener}
*/
public static final int DID_FINISH_RENDERING_FRAME = 9;
/**
* This {@link MapChange} is currently not implemented.
*
* More information in {@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.
*
* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener}
*/
public static final int WILL_START_RENDERING_MAP = 11;
/**
* This {@link MapChange} is currently not implemented.
*
* More information in {@see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener}
*/
public static final int DID_FINISH_RENDERING_MAP = 12;
/**
* This {@link MapChange} is currently not implemented.
*
* More information in {@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);
}
//
// 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
.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);
LatLng centerCoordinate = new LatLng(centerLatitude, centerLongitude);
setCenterCoordinate(centerCoordinate);
// need to set zoom level first because of limitation on rotating when zoomed out
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
setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false));
} 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) {
setCenterCoordinate((LatLng) savedInstanceState.getParcelable(STATE_CENTER_COORDINATE));
// need to set zoom level first because of limitation on rotating when zoomed out
setZoomLevel(savedInstanceState.getDouble(STATE_ZOOM_LEVEL));
setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION));
setDirection(savedInstanceState.getDouble(STATE_DIRECTION));
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));
setMyLocationEnabled(savedInstanceState.getBoolean(STATE_MY_LOCATION_ENABLED));
// 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));
}
// 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) {
reloadSprites();
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_COORDINATE, getCenterCoordinate());
// need to set zoom level first because of limitation on rotating when zoomed out
outState.putDouble(STATE_ZOOM_LEVEL, getZoomLevel());
outState.putDouble(STATE_CENTER_DIRECTION, getDirection());
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());
// 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));
mUserLocationView.resume();
mNativeMapView.resume();
mNativeMapView.update();
}
/**
* 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 coordinate at the center of the map view.
*
* @return The current coordinate.
*/
@UiThread
@NonNull
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)
*/
@UiThread
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.
*/
@UiThread
public void setCenterCoordinate(@NonNull LatLng 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.setLatLng(centerCoordinate, duration);
}
/**
* 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)
*/
@UiThread
public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate) {
setCenterCoordinate(centerCoordinate, false);
}
/**
* 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();
}
/**
* 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.
*/
@UiThread
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);
}
/**
* 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 = 0.0, to = 60.0)
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 level.
*/
@UiThread
@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL)
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 ¼ 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
*/
@UiThread
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 ¼ 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
*/
@UiThread
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;
}
//
// 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//