Created
March 29, 2014 21:57
-
-
Save isatimur/9863676 to your computer and use it in GitHub Desktop.
Support more Fragment XML attributes #251
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:mapbox="http://schemas.android.com/apk/res-auto" | |
android:orientation="vertical" | |
android:layout_width="fill_parent" | |
android:layout_height="fill_parent" > | |
<com.mapbox.mapboxsdk.views.MapView | |
android:id="@+id/mapview" | |
android:layout_width="fill_parent" | |
android:layout_height="fill_parent" | |
mapbox:mapid="examples.map-zgrqqx0w" | |
mapbox:centerLat="44.98777915289962" | |
mapbox:centerLng="38.93113195896149" | |
mapbox:zoomLvl="11"> | |
</com.mapbox.mapboxsdk.views.MapView> | |
<LinearLayout | |
android:id="@+id/buttonsrow" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" | |
android:layout_alignParentBottom="true" | |
android:orientation="horizontal" | |
android:weightSum="1"> | |
<Button | |
android:id="@+id/satbut" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" | |
android:text="Satellite" | |
android:layout_weight="0.3333" android:layout_margin="-5dp" android:background="#3887be" | |
android:alpha="0.8"/> | |
<Button | |
android:id="@+id/terbut" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" | |
android:text="Terrain" | |
android:layout_weight="0.33333" android:layout_margin="-5dp" android:background="#3887be" | |
android:alpha="0.8"/> | |
<Button | |
android:id="@+id/strbut" | |
android:layout_width="fill_parent" | |
android:layout_height="wrap_content" | |
android:text="Streets" | |
android:layout_weight="0.3333" android:background="#3887be" android:alpha="0.8"/> | |
<Button | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="+" | |
android:id="@+id/layerselect" | |
android:alpha="0.8" | |
android:background="#3887BE" /> | |
</LinearLayout> | |
<ImageView android:layout_width="100dp" android:layout_height="65dp" android:layout_alignParentTop="true" | |
android:layout_alignParentRight="true" android:src="@drawable/mblogo" android:layout_marginTop="-10dp"/> | |
</RelativeLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="MapView"> | |
<attr name="mapid" format="string"/> | |
<attr name="centerLat" format="string"/> | |
<attr name="centerLng" format="string"/> | |
<attr name="zoomLvl" format="string"/> | |
<attr name="uiControls" format="string"/> | |
</declare-styleable> | |
</resources> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.mapbox.mapboxsdk.android.testapp; | |
import android.app.AlertDialog; | |
import android.content.DialogInterface; | |
import android.content.Intent; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.os.Bundle; | |
import android.provider.Settings; | |
import android.support.v7.app.ActionBarActivity; | |
import android.view.Menu; | |
import android.view.View; | |
import android.view.Window; | |
import android.widget.Button; | |
import com.mapbox.mapboxsdk.api.ILatLng; | |
import com.mapbox.mapboxsdk.geometry.LatLng; | |
import com.mapbox.mapboxsdk.overlay.GpsLocationProvider; | |
import com.mapbox.mapboxsdk.overlay.Icon; | |
import com.mapbox.mapboxsdk.overlay.Marker; | |
import com.mapbox.mapboxsdk.overlay.PathOverlay; | |
import com.mapbox.mapboxsdk.overlay.UserLocationOverlay; | |
import com.mapbox.mapboxsdk.tileprovider.tilesource.*; | |
import com.mapbox.mapboxsdk.views.MapController; | |
import com.mapbox.mapboxsdk.views.MapView; | |
import com.mapbox.mapboxsdk.views.util.TilesLoadedListener; | |
public class MainActivity extends ActionBarActivity { | |
private MapController mapController; | |
private LatLng startingPoint = new LatLng(51f, 0f); | |
private MapView mv; | |
private UserLocationOverlay myLocationOverlay; | |
private Paint paint; | |
private String satellite = "brunosan.map-cyglrrfu"; | |
private String street = "examples.map-vyofok3q"; | |
private String terrain = "examples.map-zgrqqx0w"; | |
private String currentLayer = "terrain"; | |
private PathOverlay equator; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
this.requestWindowFeature(Window.FEATURE_NO_TITLE); | |
setContentView(R.layout.activity_main); | |
mv = (MapView) findViewById(R.id.mapview); | |
mapController = mv.getController(); | |
//replaceMapView("opencycle"); | |
//replaceMapView(terrain); | |
addLocationOverlay(); | |
mv.loadFromGeoJSONURL("https://gist.github.com/fdansv/8541618/raw/09da8aef983c8ffeb814d0a1baa8ecf563555b5d/geojsonpointtest"); | |
setButtonListeners(); | |
Marker m = new Marker(mv, "Edinburgh", "Scotland", new LatLng(55.94629, -3.20777)); | |
m.setIcon(new Icon(getResources(), Icon.Size.SMALL, "marker-stroked", "FF0000")); | |
mv.addMarker(m); | |
m = new Marker(mv, "Stockholm", "Sweden", new LatLng(59.32995, 18.06461)); | |
m.setIcon(new Icon(getResources(), Icon.Size.MEDIUM, "city", "FFFF00")); | |
mv.addMarker(m); | |
m = new Marker(mv, "Prague", "Czech Republic", new LatLng(50.08734, 14.42112)); | |
m.setIcon(new Icon(getResources(), Icon.Size.LARGE, "land-use", "00FFFF")); | |
mv.addMarker(m); | |
m = new Marker(mv, "Athens", "Greece", new LatLng(37.97885, 23.71399)); | |
mv.addMarker(m); | |
mv.setOnTilesLoadedListener(new TilesLoadedListener() { | |
@Override | |
public boolean onTilesLoaded() { | |
return false; | |
} | |
@Override | |
public boolean onTilesLoadStarted() { | |
// TODO Auto-generated method stub | |
return false; | |
} | |
}); | |
mv.setVisibility(View.VISIBLE); | |
equator = new PathOverlay(); | |
equator.addPoint(0, -89); | |
equator.addPoint(0, 89); | |
mv.getOverlays().add(equator); | |
} | |
private void setButtonListeners() { | |
Button satBut = changeButtonTypeface((Button) findViewById(R.id.satbut)); | |
satBut.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
if (!currentLayer.equals("satellite")) { | |
replaceMapView(satellite); | |
currentLayer = "satellite"; | |
} | |
} | |
}); | |
Button terBut = changeButtonTypeface((Button) findViewById(R.id.terbut)); | |
terBut.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
if (!currentLayer.equals("terrain")) { | |
replaceMapView(terrain); | |
currentLayer = "terrain"; | |
} | |
} | |
}); | |
Button strBut = changeButtonTypeface((Button) findViewById(R.id.strbut)); | |
strBut.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
if (!currentLayer.equals("street")) { | |
replaceMapView(street); | |
currentLayer = "street"; | |
} | |
} | |
}); | |
Button selectBut = changeButtonTypeface((Button) findViewById(R.id.layerselect)); | |
selectBut.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.this); | |
ab.setTitle("Select Layer"); | |
ab.setItems(availableLayers, new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface d, int choice) { | |
replaceMapView(availableLayers[choice]); | |
} | |
}); | |
ab.show(); | |
} | |
}); | |
} | |
final String availableLayers[] = {"OpenStreetMap", "OpenSeaMap", "open-streets-dc.mbtiles"}; | |
protected void replaceMapView(String layer) { | |
ITileLayer source; | |
if (layer.toLowerCase().endsWith("mbtiles")) { | |
mv.setTileSource(new ITileLayer[]{new MBTilesLayer(this, layer)}); | |
} else { | |
if (layer.equalsIgnoreCase("OpenStreetMap")) { | |
source = new WebSourceTileLayer("http://tile.openstreetmap.org/%d/%d/%d.png") | |
.setName("OpenStreetMap") | |
.setAttribution("© OpenStreetMap Contributors") | |
.setMinimumZoomLevel(1) | |
.setMaximumZoomLevel(18); | |
} else if (layer.equalsIgnoreCase("OpenSeaMap")) { | |
source = new WebSourceTileLayer("http://tile.openstreetmap.org/seamark/%d/%d/%d.png") | |
.setName("OpenStreetMap") | |
.setAttribution("© OpenStreetMap Contributors") | |
.setMinimumZoomLevel(1) | |
.setMaximumZoomLevel(18); | |
} else { | |
source = new MapboxTileLayer(layer); | |
} | |
mv.setTileSource(source); | |
} | |
mv.setScrollableAreaLimit(mv.getTileProvider().getBoundingBox()); | |
mv.setMinZoomLevel(mv.getTileProvider().getMinimumZoomLevel()); | |
mv.setMaxZoomLevel(mv.getTileProvider().getMaximumZoomLevel()); | |
mv.setCenter(mv.getTileProvider().getCenterCoordinate()); | |
mv.setZoom(mv.getTileProvider().getCenterZoom()); | |
mv.zoomToBoundingBox(mv.getTileProvider().getBoundingBox()); | |
} | |
private void addLocationOverlay() { | |
// Adds an icon that shows location | |
myLocationOverlay = new UserLocationOverlay(new GpsLocationProvider(this), mv); | |
myLocationOverlay.enableMyLocation(); | |
myLocationOverlay.setDrawAccuracyEnabled(true); | |
mv.getOverlays().add(myLocationOverlay); | |
} | |
private void addLine() { | |
// Configures a line | |
PathOverlay po = new PathOverlay(Color.RED, this); | |
Paint linePaint = new Paint(); | |
linePaint.setStyle(Paint.Style.STROKE); | |
linePaint.setColor(Color.BLUE); | |
linePaint.setStrokeWidth(5); | |
po.setPaint(linePaint); | |
po.addPoint(startingPoint); | |
po.addPoint(new LatLng(51.7, 0.3)); | |
po.addPoint(new LatLng(51.2, 0)); | |
// Adds line and marker to the overlay | |
mv.getOverlays().add(po); | |
} | |
@Override | |
public boolean onCreateOptionsMenu(Menu menu) { | |
// Inflate the menu; this adds items to the action bar if it is present. | |
getMenuInflater().inflate(R.menu.activity_main, menu); | |
return true; | |
} | |
private Button changeButtonTypeface(Button button) { | |
//Typeface tf = Typeface.createFromAsset(this.getAssets(), "fonts/semibold.ttf"); | |
//button.setTypeface(tf); | |
return button; | |
} | |
public LatLng getMapCenter() { | |
return mv.getCenter(); | |
} | |
public void setMapCenter(ILatLng center) { | |
mv.setCenter(center); | |
} | |
/** | |
* Method to show settings in alert dialog | |
* On pressing Settings button will lauch Settings Options - GPS | |
*/ | |
public void showSettingsAlert() { | |
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getBaseContext()); | |
// Setting Dialog Title | |
alertDialog.setTitle("GPS settings"); | |
// Setting Dialog Message | |
alertDialog.setMessage("GPS is not enabled. Do you want to go to settings menu?"); | |
// On pressing Settings button | |
alertDialog.setPositiveButton("Settings", new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); | |
getBaseContext().startActivity(intent); | |
} | |
}); | |
// on pressing cancel button | |
alertDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
dialog.cancel(); | |
} | |
}); | |
// Showing Alert Message | |
alertDialog.show(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.mapbox.mapboxsdk.views; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.graphics.Canvas; | |
import android.graphics.Matrix; | |
import android.graphics.Point; | |
import android.graphics.PointF; | |
import android.graphics.Rect; | |
import android.graphics.RectF; | |
import android.os.Build; | |
import android.os.Handler; | |
import android.util.AttributeSet; | |
import android.util.Log; | |
import android.view.GestureDetector; | |
import android.view.KeyEvent; | |
import android.view.MotionEvent; | |
import android.view.ScaleGestureDetector; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.Scroller; | |
import com.mapbox.mapboxsdk.R; | |
import com.mapbox.mapboxsdk.api.ILatLng; | |
import com.mapbox.mapboxsdk.constants.MapboxConstants; | |
import com.mapbox.mapboxsdk.events.MapListener; | |
import com.mapbox.mapboxsdk.events.ScrollEvent; | |
import com.mapbox.mapboxsdk.events.ZoomEvent; | |
import com.mapbox.mapboxsdk.format.GeoJSON; | |
import com.mapbox.mapboxsdk.geometry.BoundingBox; | |
import com.mapbox.mapboxsdk.geometry.LatLng; | |
import com.mapbox.mapboxsdk.overlay.GeoJSONLayer; | |
import com.mapbox.mapboxsdk.overlay.ItemizedIconOverlay; | |
import com.mapbox.mapboxsdk.overlay.ItemizedOverlay; | |
import com.mapbox.mapboxsdk.overlay.MapEventsOverlay; | |
import com.mapbox.mapboxsdk.overlay.MapEventsReceiver; | |
import com.mapbox.mapboxsdk.overlay.Marker; | |
import com.mapbox.mapboxsdk.overlay.Overlay; | |
import com.mapbox.mapboxsdk.overlay.OverlayManager; | |
import com.mapbox.mapboxsdk.overlay.TilesOverlay; | |
import com.mapbox.mapboxsdk.tileprovider.MapTileLayerBase; | |
import com.mapbox.mapboxsdk.tileprovider.MapTileLayerBasic; | |
import com.mapbox.mapboxsdk.tileprovider.tilesource.ITileLayer; | |
import com.mapbox.mapboxsdk.tileprovider.tilesource.MapboxTileLayer; | |
import com.mapbox.mapboxsdk.tileprovider.util.SimpleInvalidationHandler; | |
import com.mapbox.mapboxsdk.util.GeometryMath; | |
import com.mapbox.mapboxsdk.util.NetworkUtils; | |
import com.mapbox.mapboxsdk.views.util.Projection; | |
import com.mapbox.mapboxsdk.views.util.TileLoadedListener; | |
import com.mapbox.mapboxsdk.views.util.TilesLoadedListener; | |
import com.mapbox.mapboxsdk.views.util.constants.MapViewConstants; | |
import com.mapbox.mapboxsdk.views.util.constants.MapViewLayouts; | |
import org.json.JSONException; | |
import java.lang.Double; | |
import java.lang.reflect.Method; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
import java.util.concurrent.atomic.AtomicInteger; | |
/** | |
* The MapView class manages all of the content and | |
* state of a single map, including layers, markers, | |
* and interaction code. | |
*/ | |
public class MapView extends ViewGroup implements MapViewConstants, MapEventsReceiver, MapboxConstants { | |
/** | |
* The default marker Overlay, automatically added to the view to add markers directly. | |
*/ | |
private ItemizedIconOverlay<Marker> defaultMarkerOverlay; | |
/** | |
* List linked to the default marker overlay. | |
*/ | |
private ArrayList<Marker> defaultMarkerList = new ArrayList<Marker>(); | |
/** | |
* Overlay for basic map touch events. | |
*/ | |
private MapEventsOverlay eventsOverlay; | |
/** | |
* A copy of the app context. | |
*/ | |
private Context context; | |
/** | |
* Whether or not a marker has been placed already. | |
*/ | |
private boolean firstMarker = true; | |
private static final String TAG = "MapBox MapView"; | |
private static Method sMotionEventTransformMethod; | |
/** | |
* Current zoom level for map tiles. | |
*/ | |
private float mZoomLevel = 11; | |
protected float mRequestedMinimumZoomLevel = 0; | |
private float mMinimumZoomLevel = 0; | |
private float mMaximumZoomLevel = 22; | |
/** | |
* The MapView listener | |
*/ | |
private MapViewListener mMapViewListener; | |
private final OverlayManager mOverlayManager; | |
private Projection mProjection; | |
private final TilesOverlay mMapOverlay; | |
private final GestureDetector mGestureDetector; | |
/** | |
* Handles map scrolling | |
*/ | |
protected final Scroller mScroller; | |
protected boolean mIsFlinging; | |
protected final AtomicInteger mTargetZoomLevel = new AtomicInteger(); | |
protected final AtomicBoolean mIsAnimating = new AtomicBoolean(false); | |
private final MapController mController; | |
protected ScaleGestureDetector mScaleGestureDetector; | |
protected float mMultiTouchScale = 1.0f; | |
protected PointF mMultiTouchScalePoint = new PointF(); | |
protected MapListener mListener; | |
private float mapOrientation = 0; | |
private final Matrix mRotateMatrix = new Matrix(); | |
private final float[] mRotatePoints = new float[2]; | |
private final Rect mInvalidateRect = new Rect(); | |
protected BoundingBox mScrollableAreaBoundingBox; | |
protected RectF mScrollableAreaLimit; | |
private BoundingBox mBoundingBoxToZoomOn = null; | |
// for speed (avoiding allocations) | |
protected final MapTileLayerBase mTileProvider; | |
private final Handler mTileRequestCompleteHandler; | |
/* a point that will be reused to design added views */ | |
private final PointF mPoint = new PointF(); | |
private TilesLoadedListener tilesLoadedListener; | |
TileLoadedListener tileLoadedListener; | |
private InfoWindow currentTooltip; | |
/** | |
* Constructor for XML layout calls. Should not be used programmatically. | |
* | |
* @param context A copy of the app context | |
* @param attrs An AttributeSet object to get extra info from the XML, such as mapbox id or type of baselayer | |
*/ | |
protected MapView(final Context context, final int tileSizePixels, MapTileLayerBase tileProvider, final Handler tileRequestCompleteHandler, final AttributeSet attrs) { | |
super(context, attrs); | |
this.mController = new MapController(this); | |
this.mScroller = new Scroller(context); | |
Projection.setTileSize(tileSizePixels); | |
if (tileProvider == null) { | |
tileProvider = new MapTileLayerBasic(context, null, this); | |
} | |
mTileRequestCompleteHandler = tileRequestCompleteHandler == null | |
? new SimpleInvalidationHandler(this) | |
: tileRequestCompleteHandler; | |
mTileProvider = tileProvider; | |
mTileProvider.setTileRequestCompleteHandler(mTileRequestCompleteHandler); | |
this.mMapOverlay = new TilesOverlay(mTileProvider); | |
mOverlayManager = new OverlayManager(mMapOverlay); | |
this.mGestureDetector = new GestureDetector(context, new MapViewGestureDetectorListener(this)); | |
mGestureDetector.setOnDoubleTapListener(new MapViewDoubleClickListener(this)); | |
mScaleGestureDetector = new ScaleGestureDetector(context, new MapViewScaleGestureDetectorListener(this)); | |
this.context = context; | |
eventsOverlay = new MapEventsOverlay(context, this); | |
this.getOverlays().add(eventsOverlay); | |
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MapView); | |
String mapid = a.getString(R.styleable.MapView_mapid); | |
if (mapid != null) { | |
setTileSource(new MapboxTileLayer(mapid)); | |
} else { | |
Log.w(MapView.class.getCanonicalName(), "mapid not set."); | |
} | |
String centerLat = a.getString(R.styleable.MapView_centerLat); | |
String centerLng = a.getString(R.styleable.MapView_centerLng); | |
if (centerLat != null && centerLng != null) { | |
double lat, lng; | |
lat = Double.parseDouble(centerLat); | |
lng = Double.parseDouble(centerLng); | |
this.setCenter(new LatLng(lat, lng)); | |
} else { | |
Log.d(MapView.class.getCanonicalName(), "centerLatLng is not specified in XML."); | |
} | |
String zoomLvl = a.getString(R.styleable.MapView_zoomLvl); | |
if (zoomLvl != null) { | |
float lvl = Float.parseFloat(zoomLvl); | |
this.setZoom(lvl); | |
} else { | |
Log.d(MapView.class.getCanonicalName(), "zoomLevel is not specified in XML."); | |
} | |
a.recycle(); | |
} | |
public MapView(final Context context, AttributeSet attrs) { | |
this(context, 256, null, null, attrs); | |
} | |
protected MapView(Context context, int tileSizePixels, MapTileLayerBase aTileProvider) { | |
this(context, tileSizePixels, aTileProvider, null, null); | |
init(context); | |
} | |
public void setTileSource(final ITileLayer[] value) { | |
if (mTileProvider != null && mTileProvider instanceof MapTileLayerBasic) { | |
((MapTileLayerBasic) mTileProvider).setTileSources(value); | |
this.setZoom(mZoomLevel); | |
postInvalidate(); | |
} | |
} | |
public void setTileSource(final ITileLayer value) { | |
ITileLayer aTileSource = value; | |
mTileProvider.setTileSource(aTileSource); | |
Projection.setTileSize(aTileSource.getTileSizePixels()); | |
this.setZoom(mZoomLevel); | |
postInvalidate(); | |
} | |
public void addTileSource(final ITileLayer aTileSource) { | |
if (mTileProvider != null && mTileProvider instanceof MapTileLayerBasic) { | |
((MapTileLayerBasic) mTileProvider).addTileSource(aTileSource); | |
this.setZoom(mZoomLevel); | |
postInvalidate(); | |
} | |
} | |
public void removeTileSource(final ITileLayer aTileSource) { | |
if (mTileProvider != null && mTileProvider instanceof MapTileLayerBasic) { | |
((MapTileLayerBasic) mTileProvider).removeTileSource(aTileSource); | |
this.setZoom(mZoomLevel); | |
postInvalidate(); | |
} | |
} | |
/** | |
* Method that constructs the view. used in lieu of a constructor. | |
* | |
* @param context a copy of the app context | |
*/ | |
private void init(Context context) { | |
this.context = context; | |
eventsOverlay = new MapEventsOverlay(context, this); | |
this.getOverlays().add(eventsOverlay); | |
} | |
/** | |
* Adds a marker to the default marker overlay | |
* | |
* @param marker the marker object to be added | |
* @return the marker object | |
*/ | |
public Marker addMarker(final Marker marker) { | |
if (firstMarker) { | |
defaultMarkerList.add(marker); | |
setDefaultItemizedOverlay(); | |
} else { | |
defaultMarkerOverlay.addItem(marker); | |
} | |
marker.addTo(this); | |
firstMarker = false; | |
return marker; | |
} | |
public void removeMarker(final Marker marker) { | |
defaultMarkerList.remove(marker); | |
defaultMarkerOverlay.removeItem(marker); | |
this.invalidate(); | |
} | |
/** | |
* Adds a new ItemizedOverlay to the MapView | |
* | |
* @param itemizedOverlay the itemized overlay | |
*/ | |
public void addItemizedOverlay(ItemizedOverlay<Marker> itemizedOverlay) { | |
this.getOverlays().add(itemizedOverlay); | |
} | |
public ArrayList<ItemizedIconOverlay> getItemizedOverlays() { | |
ArrayList<ItemizedIconOverlay> list = new ArrayList<ItemizedIconOverlay>(); | |
for (Overlay overlay : getOverlays()) { | |
if (overlay instanceof ItemizedOverlay) { | |
list.add((ItemizedIconOverlay) overlay); | |
} | |
} | |
return list; | |
} | |
/** | |
* Load and parse a GeoJSON file at a given URL | |
* | |
* @param URL the URL from which to load the GeoJSON file | |
*/ | |
public void loadFromGeoJSONURL(String URL) { | |
if (NetworkUtils.isNetworkAvailable(getContext())) { | |
new GeoJSONLayer(this).loadURL(URL); | |
} | |
} | |
/** | |
* Load and parse a GeoJSON file at a given URL | |
* | |
* @param geoJSON the GeoJSON string to parse | |
*/ | |
public void loadFromGeoJSONString(String geoJSON) throws JSONException { | |
GeoJSON.parseString(geoJSON, MapView.this); | |
} | |
private void closeCurrentTooltip(){ | |
if (currentTooltip != null) { | |
if (mMapViewListener != null) { | |
mMapViewListener.willHideMarker(this, currentTooltip.getBoundMarker()); | |
} | |
currentTooltip.close(); | |
currentTooltip = null; | |
} | |
} | |
/** | |
* Sets the default itemized overlay. | |
*/ | |
private void setDefaultItemizedOverlay() { | |
defaultMarkerOverlay = new ItemizedIconOverlay<Marker>(getContext(), defaultMarkerList, new ItemizedIconOverlay.OnItemGestureListener<Marker>() { | |
public boolean onItemSingleTapUp(final int index, | |
final Marker item) { | |
InfoWindow toolTip = item.getToolTip(MapView.this); | |
if (mMapViewListener != null) { | |
mMapViewListener.tapOnMarker(MapView.this, item); | |
} | |
// Hide tooltip if tapping on the same marker | |
if (toolTip == currentTooltip) { | |
closeCurrentTooltip(); | |
} else { | |
if (mMapViewListener != null) { | |
mMapViewListener.willShowMarker(MapView.this, item); | |
} | |
currentTooltip = toolTip; | |
item.showBubble(currentTooltip, MapView.this, true); | |
} | |
return true; | |
} | |
public boolean onItemLongPress(final int index, | |
final Marker item) { | |
if (mMapViewListener != null) { | |
mMapViewListener.longpressOnMarker(MapView.this, item); | |
} | |
return true; | |
} | |
}); | |
this.getOverlays().add(defaultMarkerOverlay); | |
} | |
/** | |
* @param p the position where the event occurred. | |
* @return whether the event action is triggered or not | |
*/ | |
public boolean singleTapUpHelper(final ILatLng p) { | |
closeCurrentTooltip(); | |
onTap(p); | |
return true; | |
} | |
/** | |
* @param p the position where the event occurred. | |
* @return whether the event action is triggered or not | |
*/ | |
public boolean longPressHelper(final ILatLng p) { | |
onLongPress(p); | |
return false; | |
} | |
public void onLongPress(final ILatLng p) { | |
if (mMapViewListener != null) { | |
mMapViewListener.longpressOnMap(MapView.this, p); | |
} | |
} | |
public void onTap(final ILatLng p) { | |
if (mMapViewListener != null) { | |
mMapViewListener.tapOnMap(MapView.this, p); | |
} | |
} | |
public MapController getController() { | |
return this.mController; | |
} | |
public TilesOverlay getMapOverlay() { | |
return mMapOverlay; | |
} | |
/** | |
* You can add/remove/reorder your Overlays using the List of {@link Overlay}. The first (index | |
* 0) Overlay gets drawn first, the one with the highest as the last one. | |
*/ | |
public List<Overlay> getOverlays() { | |
return this.getOverlayManager(); | |
} | |
public OverlayManager getOverlayManager() { | |
return mOverlayManager; | |
} | |
public MapTileLayerBase getTileProvider() { | |
return mTileProvider; | |
} | |
public Scroller getScroller() { | |
return mScroller; | |
} | |
public Handler getTileRequestCompleteHandler() { | |
return mTileRequestCompleteHandler; | |
} | |
/** | |
* Compute the current geographical bounding box for this map. | |
* | |
* @return the current bounds of the map | |
*/ | |
public BoundingBox getBoundingBox() { | |
int w = getWidth(); | |
int h = getHeight(); | |
if (w > 0 && h > 0) { | |
return getBoundingBox(getWidth(), getHeight()); | |
} | |
return null; | |
} | |
private BoundingBox getBoundingBox(final int pViewWidth, final int pViewHeight) { | |
final int world_2 = Projection.mapSize(mZoomLevel) / 2; | |
final Rect screenRect = getScreenRect(null); | |
screenRect.offset(world_2, world_2); | |
final ILatLng neGeoPoint = Projection.pixelXYToLatLong(screenRect.right, screenRect.top, | |
mZoomLevel); | |
final ILatLng swGeoPoint = Projection.pixelXYToLatLong(screenRect.left, | |
screenRect.bottom, mZoomLevel); | |
return new BoundingBox(neGeoPoint.getLatitude(), neGeoPoint.getLongitude(), | |
swGeoPoint.getLatitude(), swGeoPoint.getLongitude()); | |
} | |
/** | |
* Get centerpoint of the phone as latitude and longitude. | |
* | |
* @return centerpoint | |
*/ | |
public LatLng getCenter() { | |
return getBoundingBox().getCenter(); | |
} | |
/** | |
* Gets the current bounds of the screen in <I>screen coordinates</I>. | |
*/ | |
public Rect getScreenRect(final Rect reuse) { | |
final Rect out = getIntrinsicScreenRect(reuse); | |
if (this.getMapOrientation() != 0 && this.getMapOrientation() != 180) { | |
// Since the canvas is shifted by getWidth/2, we can just return our natural scrollX/Y | |
// value since that is the same as the shifted center. | |
int centerX = this.getScrollX(); | |
int centerY = this.getScrollY(); | |
GeometryMath.getBoundingBoxForRotatedRectangle(out, centerX, centerY, | |
this.getMapOrientation(), out); | |
} | |
return out; | |
} | |
public Rect getIntrinsicScreenRect(final Rect reuse) { | |
final Rect out; | |
if (reuse == null) { | |
out = new Rect(); | |
} else { | |
out = reuse; | |
} | |
out.set(getScrollX() - getMeasuredWidth() / 2, getScrollY() - getMeasuredHeight() / 2, getScrollX() | |
+ getMeasuredWidth() / 2, getScrollY() + getMeasuredHeight() / 2); | |
return out; | |
} | |
/** | |
* Get a projection for converting between screen-pixel coordinates and latitude/longitude | |
* coordinates. You should not hold on to this object for more than one draw, since the | |
* projection of the map could change. | |
* | |
* @return The Projection of the map in its current state. You should not hold on to this object | |
* for more than one draw, since the projection of the map could change. | |
*/ | |
public Projection getProjection() { | |
if (mProjection == null) { | |
mProjection = new Projection(this); | |
} | |
return mProjection; | |
} | |
/** | |
* Set the centerpoint of the map view, given a latitude and | |
* longitude position. | |
* | |
* @param aCenter | |
* @return the map view, for chaining | |
*/ | |
public MapView setCenter(final ILatLng aCenter) { | |
getController().setCenter(aCenter); | |
return this; | |
} | |
public MapView panBy(int x, int y) { | |
scrollBy(x, y); | |
return this; | |
} | |
public MapView setScale(float scale) { | |
float zoomDelta; | |
if (scale < 1) { | |
zoomDelta = -2 * (1 - scale); | |
} else { | |
zoomDelta = scale - 1.0f; | |
} | |
float newZoom = mZoomLevel + zoomDelta; | |
if (newZoom <= mMaximumZoomLevel && newZoom >= mMinimumZoomLevel) { | |
mMultiTouchScale = scale; | |
invalidate(); | |
} | |
return this; | |
} | |
public float getScale() { | |
return mMultiTouchScale; | |
} | |
/** | |
* @param aZoomLevel the zoom level bound by the tile source | |
* @return the map view, for chaining | |
*/ | |
public MapView setZoom(final float aZoomLevel) { | |
return this.mController.setZoom(aZoomLevel); | |
} | |
protected MapView setZoomInternal(final float aZoomLevel) { | |
final float minZoomLevel = getMinZoomLevel(); | |
final float maxZoomLevel = getMaxZoomLevel(); | |
final float newZoomLevel = Math.max(minZoomLevel, | |
Math.min(maxZoomLevel, aZoomLevel)); | |
final float curZoomLevel = this.mZoomLevel; | |
if (newZoomLevel != curZoomLevel) { | |
mScroller.forceFinished(true); | |
mIsFlinging = false; | |
} | |
this.mZoomLevel = newZoomLevel; | |
updateScrollableAreaLimit(); | |
if (newZoomLevel > curZoomLevel) { | |
// We are going from a lower-resolution plane to a higher-resolution plane, so we have | |
// to do it the hard way. | |
final int worldSize_current_2 = Projection.mapSize(curZoomLevel) / 2; | |
final int worldSize_new_2 = Projection.mapSize(newZoomLevel) / 2; | |
final ILatLng centerGeoPoint = Projection.pixelXYToLatLong(getScrollX() | |
+ worldSize_current_2, getScrollY() + worldSize_current_2, curZoomLevel); | |
final PointF centerPoint = Projection.latLongToPixelXY( | |
centerGeoPoint.getLatitude(), centerGeoPoint.getLongitude(), | |
newZoomLevel, null); | |
scrollTo((int) centerPoint.x - worldSize_new_2, (int) centerPoint.y - worldSize_new_2); | |
} else if (newZoomLevel < curZoomLevel) { | |
// We are going from a higher-resolution plane to a lower-resolution plane, so we can do | |
// it the easy way. | |
scrollTo( | |
(int) (GeometryMath.rightShift(getScrollX(), curZoomLevel | |
- newZoomLevel)), | |
(int) (GeometryMath.rightShift(getScrollY(), curZoomLevel | |
- newZoomLevel))); | |
} | |
// scrollTo(getScrollX(), getScrollY()); | |
// snap for all snappables | |
final Point snapPoint = new Point(); | |
mProjection = new Projection(this); | |
if (this.getOverlayManager().onSnapToItem(getScrollX(), getScrollY(), snapPoint, this)) { | |
scrollTo(snapPoint.x, snapPoint.y); | |
} | |
mTileProvider.rescaleCache(newZoomLevel, curZoomLevel, mProjection); | |
// do callback on listener | |
if (newZoomLevel != curZoomLevel && mListener != null) { | |
final ZoomEvent event = new ZoomEvent(this, newZoomLevel); | |
mListener.onZoom(event); | |
} | |
// Allows any views fixed to a Location in the MapView to adjust | |
this.requestLayout(); | |
cluster(); | |
return this; | |
} | |
/** | |
* Zoom the map to enclose the specified bounding box, as closely as possible. | |
* Must be called after display layout is complete, or screen dimensions are not known, and | |
* will always zoom to center of zoom level 0. | |
* Suggestion: Check getScreenRect(null).getHeight() > 0 | |
*/ | |
public MapView zoomToBoundingBox(final BoundingBox boundingBox) { | |
if (boundingBox == null) { | |
return this; | |
} | |
final BoundingBox currentBox = getBoundingBox(); | |
if (currentBox == null) { | |
mBoundingBoxToZoomOn = boundingBox; | |
return this; | |
} | |
// Calculated required zoom based on latitude span | |
final double maxZoomLatitudeSpan = mZoomLevel == getMaxZoomLevel() ? | |
currentBox.getLatitudeSpan() : | |
currentBox.getLatitudeSpan() / Math.pow(2, getMaxZoomLevel() - mZoomLevel); | |
final double requiredLatitudeZoom = | |
getMaxZoomLevel() - | |
Math.ceil(Math.log(boundingBox.getLatitudeSpan() / maxZoomLatitudeSpan) / Math.log(2)); | |
// Calculated required zoom based on longitude span | |
final double maxZoomLongitudeSpan = mZoomLevel == getMaxZoomLevel() ? | |
currentBox.getLongitudeSpan() : | |
currentBox.getLongitudeSpan() / Math.pow(2, getMaxZoomLevel() - mZoomLevel); | |
final double requiredLongitudeZoom = getMaxZoomLevel() | |
- (Math.log(boundingBox.getLongitudeSpan() | |
/ maxZoomLongitudeSpan) / Math.log(2)); | |
// Zoom to boundingBox center, at calculated maximum allowed zoom level | |
getController().setZoom( | |
(float) Math.max( | |
Math.min(requiredLatitudeZoom, requiredLongitudeZoom), | |
getMinZoomLevel())); | |
getController().setCenter( | |
new LatLng(boundingBox.getCenter().getLatitude(), boundingBox.getCenter() | |
.getLongitude())); | |
return this; | |
} | |
public float getClampedZoomLevel(float zoom) { | |
final float minZoomLevel = getMinZoomLevel(); | |
final float maxZoomLevel = getMaxZoomLevel(); | |
return Math.max(minZoomLevel, Math.min(maxZoomLevel, zoom)); | |
} | |
/** | |
* Get the current ZoomLevel for the map tiles. | |
* | |
* @return the current ZoomLevel between 0 (equator) and 18/19(closest), depending on the tile | |
* source chosen. | |
*/ | |
public float getZoomLevel() { | |
return getZoomLevel(true); | |
} | |
private float getAnimatedZoom() { | |
return Float.intBitsToFloat(mTargetZoomLevel.get()); | |
} | |
/** | |
* Get the current ZoomLevel for the map tiles. | |
* | |
* @param aPending if true and we're animating then return the zoom level that we're animating | |
* towards, otherwise return the current zoom level | |
* @return the zoom level | |
*/ | |
public float getZoomLevel(final boolean aPending) { | |
if (aPending && isAnimating()) { | |
return getAnimatedZoom(); | |
} else { | |
return mZoomLevel; | |
} | |
} | |
/** | |
* Get the minimum allowed zoom level for the maps. | |
*/ | |
public float getMinZoomLevel() { | |
float newMinZoom = mMinimumZoomLevel; | |
float boundingDimension = Math.max(getMeasuredWidth(), | |
getMeasuredHeight()); | |
float tileSideLength = Projection.getTileSize(); | |
if (boundingDimension > 0 && tileSideLength > 0) { | |
float clampedMinZoom = (float) (Math.log(boundingDimension | |
/ tileSideLength) / Math.log(2)); | |
if (newMinZoom < clampedMinZoom) { | |
newMinZoom = clampedMinZoom; | |
} | |
} | |
if (newMinZoom < 0) { | |
newMinZoom = 0; | |
} | |
return newMinZoom; | |
} | |
/** | |
* Get the maximum allowed zoom level for the maps. | |
*/ | |
public float getMaxZoomLevel() { | |
return mMaximumZoomLevel; | |
} | |
/** | |
* Set the minimum allowed zoom level, or pass null to use the minimum zoom level from the tile | |
* provider. | |
*/ | |
public void setMinZoomLevel(float zoomLevel) { | |
mRequestedMinimumZoomLevel = mMinimumZoomLevel = zoomLevel; | |
updateMinZoomLevel(); | |
} | |
/** | |
* Set the maximum allowed zoom level, or pass null to use the maximum zoom level from the tile | |
* provider. | |
*/ | |
public void setMaxZoomLevel(float zoomLevel) { | |
mMaximumZoomLevel = zoomLevel; | |
} | |
/** | |
* Determine whether the map is at its maximum zoom | |
* | |
* @return whether the map can zoom in | |
*/ | |
protected boolean canZoomIn() { | |
final float maxZoomLevel = getMaxZoomLevel(); | |
if ((isAnimating() ? getAnimatedZoom() : mZoomLevel) >= maxZoomLevel) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Determine whether the map is at its minimum zoom | |
* | |
* @return whether the map can zoom out | |
*/ | |
protected boolean canZoomOut() { | |
final float minZoomLevel = getMinZoomLevel(); | |
if ((isAnimating() ? getAnimatedZoom() : mZoomLevel) <= minZoomLevel) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Zoom in by one zoom level. | |
*/ | |
public boolean zoomIn() { | |
return getController().zoomIn(); | |
} | |
public boolean zoomInFixing(final ILatLng point) { | |
return getController().zoomInAbout(point); | |
} | |
/** | |
* Zoom out by one zoom level. | |
*/ | |
public boolean zoomOut() { | |
return getController().zoomOut(); | |
} | |
public boolean zoomOutFixing(final ILatLng point) { | |
return getController().zoomOutAbout(point); | |
} | |
public void setMapOrientation(float degrees) { | |
this.mapOrientation = degrees % 360.0f; | |
this.invalidate(); | |
} | |
public float getMapOrientation() { | |
return mapOrientation; | |
} | |
/** | |
* Whether to use the network connection if it's available. | |
*/ | |
public boolean useDataConnection() { | |
return mMapOverlay.useDataConnection(); | |
} | |
/** | |
* Set whether to use the network connection if it's available. | |
* | |
* @param aMode if true use the network connection if it's available. if false don't use the | |
* network connection even if it's available. | |
*/ | |
public void setUseDataConnection(final boolean aMode) { | |
mMapOverlay.setUseDataConnection(aMode); | |
} | |
private void updateMinZoomLevel() { | |
if (mScrollableAreaBoundingBox == null) { | |
return; | |
} | |
final BoundingBox currentBox = getBoundingBox(); | |
if (currentBox == null) { | |
return; | |
} | |
// Calculated required zoom based on latitude span | |
final double maxZoomLatitudeSpan = mZoomLevel == getMaxZoomLevel() ? | |
currentBox.getLatitudeSpan() : | |
currentBox.getLatitudeSpan() / Math.pow(2, getMaxZoomLevel() - mZoomLevel); | |
final double requiredLatitudeZoom = | |
getMaxZoomLevel() - | |
Math.log(mScrollableAreaBoundingBox.getLatitudeSpan() / maxZoomLatitudeSpan) / Math.log(2); | |
// Calculated required zoom based on longitude span | |
final double maxZoomLongitudeSpan = mZoomLevel == getMaxZoomLevel() ? | |
currentBox.getLongitudeSpan() : | |
currentBox.getLongitudeSpan() / Math.pow(2, getMaxZoomLevel() - mZoomLevel); | |
final double requiredLongitudeZoom = getMaxZoomLevel() | |
- (Math.log(mScrollableAreaBoundingBox.getLongitudeSpan() | |
/ maxZoomLongitudeSpan) / Math.log(2)); | |
mMinimumZoomLevel = (float)Math.min(mRequestedMinimumZoomLevel, Math.max(requiredLatitudeZoom, requiredLongitudeZoom)) ; | |
if (mZoomLevel < mMinimumZoomLevel) { | |
setZoom(mMinimumZoomLevel); | |
} | |
} | |
public void updateScrollableAreaLimit() { | |
if (mScrollableAreaBoundingBox == null) { | |
return; | |
} | |
float zoom = getZoomLevel(); | |
// if (isAnimating()) { | |
// zoom = mZoomLevel + (zoom - mZoomLevel); | |
// } | |
final int worldSize_2 = Projection.mapSize(zoom) / 2; | |
// Get NW/upper-left | |
final PointF upperLeft = Projection.latLongToPixelXY(mScrollableAreaBoundingBox.getLatNorth(), | |
mScrollableAreaBoundingBox.getLonWest(), zoom, null); | |
upperLeft.offset(-worldSize_2, -worldSize_2); | |
// Get SE/lower-right | |
final PointF lowerRight = Projection.latLongToPixelXY(mScrollableAreaBoundingBox.getLatSouth(), | |
mScrollableAreaBoundingBox.getLonEast(), zoom, null); | |
lowerRight.offset(-worldSize_2, -worldSize_2); | |
if (mScrollableAreaLimit == null) { | |
mScrollableAreaLimit = new RectF(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); | |
} else { | |
mScrollableAreaLimit.set(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); | |
} | |
} | |
/** | |
* Set the map to limit it's scrollable view to the specified BoundingBox. Note this does not | |
* limit zooming so it will be possible for the user to zoom to an area that is larger than the | |
* limited area. | |
* | |
* @param boundingBox A lat/long bounding box to limit scrolling to, or null to remove any scrolling | |
* limitations | |
*/ | |
public void setScrollableAreaLimit(BoundingBox boundingBox) { | |
mScrollableAreaBoundingBox = boundingBox; | |
// Clear scrollable area limit if null passed. | |
if (mScrollableAreaBoundingBox == null) { | |
mMinimumZoomLevel = mRequestedMinimumZoomLevel; | |
mScrollableAreaLimit = null; | |
return; | |
} | |
updateMinZoomLevel(); | |
updateScrollableAreaLimit(); | |
} | |
public BoundingBox getScrollableAreaLimit() { | |
return mScrollableAreaBoundingBox; | |
} | |
public void invalidateMapCoordinates(final Rect dirty) { | |
mInvalidateRect.set(dirty); | |
final int width_2 = this.getWidth() / 2; | |
final int height_2 = this.getHeight() / 2; | |
// Since the canvas is shifted by getWidth/2, we can just return our natural scrollX/Y value | |
// since that is the same as the shifted center. | |
int centerX = this.getScrollX(); | |
int centerY = this.getScrollY(); | |
if (this.getMapOrientation() != 0) { | |
GeometryMath.getBoundingBoxForRotatedRectangle(mInvalidateRect, centerX, centerY, | |
this.getMapOrientation() + 180, mInvalidateRect); | |
} | |
mInvalidateRect.offset(width_2, height_2); | |
super.invalidate(mInvalidateRect); | |
} | |
/** | |
* Returns a set of layout parameters with a width of | |
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, a height of | |
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} at the {@link com.mapbox.mapboxsdk.geometry.LatLng} (0, 0) align | |
* with {@link MapView.LayoutParams#BOTTOM_CENTER}. | |
*/ | |
@Override | |
protected ViewGroup.LayoutParams generateDefaultLayoutParams() { | |
return new MapView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, | |
ViewGroup.LayoutParams.WRAP_CONTENT, null, MapView.LayoutParams.BOTTOM_CENTER, 0, 0); | |
} | |
@Override | |
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) { | |
return new MapView.LayoutParams(getContext(), attrs); | |
} | |
// Override to allow type-checking of LayoutParams. | |
@Override | |
protected boolean checkLayoutParams(final ViewGroup.LayoutParams p) { | |
return p instanceof MapView.LayoutParams; | |
} | |
@Override | |
protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) { | |
return new MapView.LayoutParams(p); | |
} | |
@Override | |
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { | |
final int count = getChildCount(); | |
int maxHeight = 0; | |
int maxWidth = 0; | |
// Find out how big everyone wants to be | |
measureChildren(widthMeasureSpec, heightMeasureSpec); | |
// Find rightmost and bottom-most child | |
for (int i = 0; i < count; i++) { | |
final View child = getChildAt(i); | |
if (child.getVisibility() != GONE) { | |
final MapView.LayoutParams lp = (MapView.LayoutParams) child.getLayoutParams(); | |
final int childHeight = child.getMeasuredHeight(); | |
final int childWidth = child.getMeasuredWidth(); | |
getProjection().toMapPixels(lp.geoPoint, mPoint); | |
final int x = (int) mPoint.x + getWidth() / 2; | |
final int y = (int) mPoint.y + getHeight() / 2; | |
int childRight = x; | |
int childBottom = y; | |
switch (lp.alignment) { | |
case MapView.LayoutParams.TOP_LEFT: | |
childRight = x + childWidth; | |
childBottom = y; | |
break; | |
case MapView.LayoutParams.TOP_CENTER: | |
childRight = x + childWidth / 2; | |
childBottom = y; | |
break; | |
case MapView.LayoutParams.TOP_RIGHT: | |
childRight = x; | |
childBottom = y; | |
break; | |
case MapView.LayoutParams.CENTER_LEFT: | |
childRight = x + childWidth; | |
childBottom = y + childHeight / 2; | |
break; | |
case MapView.LayoutParams.CENTER: | |
childRight = x + childWidth / 2; | |
childBottom = y + childHeight / 2; | |
break; | |
case MapView.LayoutParams.CENTER_RIGHT: | |
childRight = x; | |
childBottom = y + childHeight / 2; | |
break; | |
case MapView.LayoutParams.BOTTOM_LEFT: | |
childRight = x + childWidth; | |
childBottom = y + childHeight; | |
break; | |
case MapView.LayoutParams.BOTTOM_CENTER: | |
childRight = x + childWidth / 2; | |
childBottom = y + childHeight; | |
break; | |
case MapView.LayoutParams.BOTTOM_RIGHT: | |
childRight = x; | |
childBottom = y + childHeight; | |
break; | |
} | |
childRight += lp.offsetX; | |
childBottom += lp.offsetY; | |
maxWidth = Math.max(maxWidth, childRight); | |
maxHeight = Math.max(maxHeight, childBottom); | |
} | |
} | |
// Account for padding too | |
maxWidth += getPaddingLeft() + getPaddingRight(); | |
maxHeight += getPaddingTop() + getPaddingBottom(); | |
// Check against minimum height and width | |
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); | |
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); | |
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), | |
resolveSize(maxHeight, heightMeasureSpec)); | |
} | |
@Override | |
protected void onLayout(final boolean changed, final int l, final int t, final int r, | |
final int b) { | |
final int count = getChildCount(); | |
if (changed) { | |
updateMinZoomLevel(); | |
float minZoom = getMinZoomLevel(); | |
if (mZoomLevel < minZoom) { | |
setZoom(minZoom); | |
} | |
if (mBoundingBoxToZoomOn != null) { | |
zoomToBoundingBox(mBoundingBoxToZoomOn); | |
mBoundingBoxToZoomOn = null; | |
} | |
} | |
for (int i = 0; i < count; i++) { | |
final View child = getChildAt(i); | |
if (child.getVisibility() != GONE) { | |
final MapView.LayoutParams lp = (MapView.LayoutParams) child.getLayoutParams(); | |
final int childHeight = child.getMeasuredHeight(); | |
final int childWidth = child.getMeasuredWidth(); | |
getProjection().toMapPixels(lp.geoPoint, mPoint); | |
final int x = (int) mPoint.x + getWidth() / 2; | |
final int y = (int) mPoint.y + getHeight() / 2; | |
int childLeft = x; | |
int childTop = y; | |
switch (lp.alignment) { | |
case MapView.LayoutParams.TOP_LEFT: | |
childLeft = getPaddingLeft() + x; | |
childTop = getPaddingTop() + y; | |
break; | |
case MapView.LayoutParams.TOP_CENTER: | |
childLeft = getPaddingLeft() + x - childWidth / 2; | |
childTop = getPaddingTop() + y; | |
break; | |
case MapView.LayoutParams.TOP_RIGHT: | |
childLeft = getPaddingLeft() + x - childWidth; | |
childTop = getPaddingTop() + y; | |
break; | |
case MapView.LayoutParams.CENTER_LEFT: | |
childLeft = getPaddingLeft() + x; | |
childTop = getPaddingTop() + y - childHeight / 2; | |
break; | |
case MapView.LayoutParams.CENTER: | |
childLeft = getPaddingLeft() + x - childWidth / 2; | |
childTop = getPaddingTop() + y - childHeight / 2; | |
break; | |
case MapView.LayoutParams.CENTER_RIGHT: | |
childLeft = getPaddingLeft() + x - childWidth; | |
childTop = getPaddingTop() + y - childHeight / 2; | |
break; | |
case MapView.LayoutParams.BOTTOM_LEFT: | |
childLeft = getPaddingLeft() + x; | |
childTop = getPaddingTop() + y - childHeight; | |
break; | |
case MapView.LayoutParams.BOTTOM_CENTER: | |
childLeft = getPaddingLeft() + x - childWidth / 2; | |
childTop = getPaddingTop() + y - childHeight; | |
break; | |
case MapView.LayoutParams.BOTTOM_RIGHT: | |
childLeft = getPaddingLeft() + x - childWidth; | |
childTop = getPaddingTop() + y - childHeight; | |
break; | |
} | |
childLeft += lp.offsetX; | |
childTop += lp.offsetY; | |
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); | |
} | |
} | |
} | |
public void onDetach() { | |
this.getOverlayManager().onDetach(this); | |
mTileProvider.detach(); | |
} | |
@Override | |
public boolean onKeyDown(final int keyCode, final KeyEvent event) { | |
final boolean result = this.getOverlayManager().onKeyDown(keyCode, event, this); | |
return result || super.onKeyDown(keyCode, event); | |
} | |
@Override | |
public boolean onKeyUp(final int keyCode, final KeyEvent event) { | |
final boolean result = this.getOverlayManager().onKeyUp(keyCode, event, this); | |
return result || super.onKeyUp(keyCode, event); | |
} | |
@Override | |
public boolean onTrackballEvent(final MotionEvent event) { | |
if (this.getOverlayManager().onTrackballEvent(event, this)) { | |
return true; | |
} | |
scrollBy((int) (event.getX() * 25), (int) (event.getY() * 25)); | |
return super.onTrackballEvent(event); | |
} | |
private boolean canTapTwoFingers = false; | |
private int multiTouchDownCount = 0; | |
private boolean handleTwoFingersTap(MotionEvent event) { | |
if (!isAnimating()) { | |
int pointerCount = event.getPointerCount(); | |
for (int i = 0; i < pointerCount; i++) { | |
int action = event.getActionMasked(); | |
switch (action) { | |
case MotionEvent.ACTION_DOWN: | |
multiTouchDownCount = 0; | |
break; | |
case MotionEvent.ACTION_UP: | |
if (canTapTwoFingers) { | |
canTapTwoFingers = false; | |
final ILatLng center = getProjection().fromPixels(event.getX(), event.getY()); | |
mController.zoomOutAbout(center); | |
return true; | |
} | |
break; | |
case MotionEvent.ACTION_POINTER_DOWN: | |
multiTouchDownCount++; | |
canTapTwoFingers = multiTouchDownCount > 1; | |
break; | |
case MotionEvent.ACTION_POINTER_UP: | |
multiTouchDownCount--; | |
break; | |
default: | |
} | |
} | |
} | |
return false; | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) { | |
// Get rotated event for some touch listeners. | |
MotionEvent rotatedEvent = rotateTouchEvent(event); | |
try { | |
if (this.getOverlayManager().onTouchEvent(rotatedEvent, this)) { | |
Log.d(TAG, "OverlayManager handled onTouchEvent"); | |
return true; | |
} | |
boolean handled = mScaleGestureDetector.onTouchEvent(event); | |
if (!mScaleGestureDetector.isInProgress()) { | |
handled |= mGestureDetector.onTouchEvent(rotatedEvent); | |
} | |
if ((mScaleGestureDetector.isInProgress()) && canTapTwoFingers) { | |
canTapTwoFingers = false; | |
} | |
handleTwoFingersTap(rotatedEvent); | |
return handled; | |
} finally { | |
if (rotatedEvent != event) { | |
rotatedEvent.recycle(); | |
} | |
} | |
} | |
private MotionEvent rotateTouchEvent(MotionEvent ev) { | |
if (this.getMapOrientation() == 0) { | |
return ev; | |
} | |
mRotateMatrix.setRotate(-getMapOrientation(), this.getWidth() / 2, this.getHeight() / 2); | |
MotionEvent rotatedEvent = MotionEvent.obtain(ev); | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { | |
mRotatePoints[0] = ev.getX(); | |
mRotatePoints[1] = ev.getY(); | |
mRotateMatrix.mapPoints(mRotatePoints); | |
rotatedEvent.setLocation(mRotatePoints[0], mRotatePoints[1]); | |
} else { | |
// This method is preferred since it will rotate historical touch events too | |
try { | |
if (sMotionEventTransformMethod == null) { | |
sMotionEventTransformMethod = MotionEvent.class.getDeclaredMethod("transform", | |
new Class[]{Matrix.class}); | |
} | |
sMotionEventTransformMethod.invoke(rotatedEvent, mRotateMatrix); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
return rotatedEvent; | |
} | |
@Override | |
public void computeScroll() { | |
if (mScroller.computeScrollOffset()) { | |
if (mScroller.isFinished()) { | |
// One last scrollTo to get to the final destination | |
scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); | |
// This will facilitate snapping-to any Snappable points. | |
setZoom(mZoomLevel); | |
mIsFlinging = false; | |
} else { | |
scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); | |
} | |
postInvalidate(); // Keep on drawing until the animation has | |
// finished. | |
} | |
} | |
public void updateScrollDuringAnimation() { | |
// updateScrollableAreaLimit(); | |
// scrollTo(getScrollX(), getScrollY()); | |
} | |
@Override | |
public void scrollTo(int x, int y) { | |
if (mScrollableAreaLimit != null) { | |
final float width_2 = this.getMeasuredWidth() / 2; | |
final float height_2 = this.getMeasuredHeight() / 2; | |
// Adjust if we are outside the scrollable area | |
if (mScrollableAreaLimit.width() <= width_2 * 2) { | |
if (x - width_2 > mScrollableAreaLimit.left) { | |
x = (int) (mScrollableAreaLimit.left + width_2); | |
} else if (x + width_2 < mScrollableAreaLimit.right) { | |
x = (int) (mScrollableAreaLimit.right - width_2); | |
} | |
} else if (x - width_2 < mScrollableAreaLimit.left) { | |
x = (int) (mScrollableAreaLimit.left + width_2); | |
} else if (x + width_2 > mScrollableAreaLimit.right) { | |
x = (int) (mScrollableAreaLimit.right - width_2); | |
} | |
if (mScrollableAreaLimit.height() <= height_2 * 2) { | |
if (y - height_2 > mScrollableAreaLimit.top) { | |
y = (int) (mScrollableAreaLimit.top + height_2); | |
} else if (y + height_2 < mScrollableAreaLimit.bottom) { | |
y = (int) (mScrollableAreaLimit.bottom - height_2); | |
} | |
} else if (y - height_2 < mScrollableAreaLimit.top) { | |
y = (int) (mScrollableAreaLimit.top + height_2); | |
} else if (y + height_2 > mScrollableAreaLimit.bottom) { | |
y = (int) (mScrollableAreaLimit.bottom - height_2); | |
} | |
} | |
super.scrollTo(x, y); | |
// do callback on listener | |
if (mListener != null) { | |
final ScrollEvent event = new ScrollEvent(this, x, y); | |
mListener.onScroll(event); | |
} | |
} | |
@Override | |
public void setBackgroundColor(final int pColor) { | |
mMapOverlay.setLoadingBackgroundColor(pColor); | |
invalidate(); | |
} | |
@Override | |
protected void dispatchDraw(final Canvas c) { | |
mProjection = new Projection(this); | |
// Save the current canvas matrix | |
c.save(); | |
c.translate(getWidth() / 2, getHeight() / 2); | |
c.scale(mMultiTouchScale, mMultiTouchScale, mMultiTouchScalePoint.x, | |
mMultiTouchScalePoint.y); | |
// rotate Canvas | |
c.rotate(mapOrientation, | |
mProjection.getScreenRect().exactCenterX(), | |
mProjection.getScreenRect().exactCenterY()); | |
// Draw all Overlays. | |
this.getOverlayManager().onDraw(c, this); | |
c.restore(); | |
super.dispatchDraw(c); | |
} | |
/** | |
* Returns true if the safe drawing canvas is being used. | |
* | |
* @see {@link com.mapbox.mapboxsdk.views.safecanvas.ISafeCanvas} | |
*/ | |
public boolean isUsingSafeCanvas() { | |
return this.getOverlayManager().isUsingSafeCanvas(); | |
} | |
/** | |
* Sets whether the safe drawing canvas is being used. | |
* | |
* @see {@link com.mapbox.mapboxsdk.views.safecanvas.ISafeCanvas} | |
*/ | |
public void setUseSafeCanvas(boolean useSafeCanvas) { | |
this.getOverlayManager().setUseSafeCanvas(useSafeCanvas); | |
} | |
@Override | |
protected void onDetachedFromWindow() { | |
this.onDetach(); | |
super.onDetachedFromWindow(); | |
} | |
/** | |
* Determines if maps are animating a zoom operation. Useful for overlays to avoid recalculating | |
* during an animation sequence. | |
* | |
* @return boolean indicating whether view is animating. | |
*/ | |
public boolean isAnimating() { | |
return mIsAnimating.get(); | |
} | |
public TileLoadedListener getTileLoadedListener() { | |
return tileLoadedListener; | |
} | |
public void cluster() { | |
for (ItemizedIconOverlay overlay : getItemizedOverlays()) { | |
if (!overlay.isClusterOverlay()) { | |
overlay.cluster(this, context); | |
} | |
} | |
} | |
// =========================================================== | |
// Public Classes | |
// =========================================================== | |
/** | |
* Per-child layout information associated with OpenStreetMapView. | |
*/ | |
public static class LayoutParams extends ViewGroup.LayoutParams implements MapViewLayouts { | |
/** | |
* The location of the child within the map view. | |
*/ | |
public ILatLng geoPoint; | |
/** | |
* The alignment the alignment of the view compared to the location. | |
*/ | |
public int alignment; | |
public int offsetX; | |
public int offsetY; | |
/** | |
* Creates a new set of layout parameters with the specified width, height and location. | |
* | |
* @param width the width, either {@link #FILL_PARENT}, {@link #WRAP_CONTENT} or a fixed size | |
* in pixels | |
* @param height the height, either {@link #FILL_PARENT}, {@link #WRAP_CONTENT} or a fixed size | |
* in pixels | |
* @param aGeoPoint the location of the child within the map view | |
* @param alignment the alignment of the view compared to the location {@link #BOTTOM_CENTER}, | |
* {@link #BOTTOM_LEFT}, {@link #BOTTOM_RIGHT} {@link #TOP_CENTER}, | |
* {@link #TOP_LEFT}, {@link #TOP_RIGHT} | |
* @param offsetX the additional X offset from the alignment location to draw the child within | |
* the map view | |
* @param offsetY the additional Y offset from the alignment location to draw the child within | |
* the map view | |
*/ | |
public LayoutParams(final int width, final int height, final ILatLng aGeoPoint, | |
final int alignment, final int offsetX, final int offsetY) { | |
super(width, height); | |
if (aGeoPoint != null) { | |
this.geoPoint = aGeoPoint; | |
} else { | |
this.geoPoint = new LatLng(0, 0); | |
} | |
this.alignment = alignment; | |
this.offsetX = offsetX; | |
this.offsetY = offsetY; | |
} | |
/** | |
* Since we cannot use XML files in this project this constructor is useless. Creates a new | |
* set of layout parameters. The values are extracted from the supplied attributes set and | |
* context. | |
* | |
* @param c the application environment | |
* @param attrs the set of attributes fom which to extract the layout parameters values | |
*/ | |
public LayoutParams(final Context c, final AttributeSet attrs) { | |
super(c, attrs); | |
this.geoPoint = new LatLng(0, 0); | |
this.alignment = BOTTOM_CENTER; | |
} | |
public LayoutParams(final ViewGroup.LayoutParams source) { | |
super(source); | |
} | |
} | |
public void setmMapViewListener(MapViewListener listener) { | |
this.mMapViewListener = listener; | |
} | |
public void setOnTileLoadedListener(TileLoadedListener tileLoadedListener) { | |
this.tileLoadedListener = tileLoadedListener; | |
} | |
public void setOnTilesLoadedListener(TilesLoadedListener tilesLoadedListener) { | |
this.tilesLoadedListener = tilesLoadedListener; | |
} | |
public TilesLoadedListener getTilesLoadedListener() { | |
return tilesLoadedListener; | |
} | |
@Override | |
public String toString() { | |
return "MapView {" + getTileProvider() + "}"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment