Merge branch 'master' into 2244-texture-view

# Conflicts:
#	android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java
This commit is contained in:
Brad Leege
2015-09-09 14:59:42 -05:00
30 changed files with 1175 additions and 261 deletions

View File

@@ -28,9 +28,9 @@ repositories {
}
dependencies {
compile 'com.android.support:support-annotations:23.0.0'
compile 'com.android.support:support-v4:23.0.0'
compile 'com.android.support:design:23.0.0'
compile 'com.android.support:support-annotations:23.0.1'
compile 'com.android.support:support-v4:23.0.1'
compile 'com.android.support:design:23.0.1'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.mapzen.android:lost:1.0.1'
}

View File

@@ -2,8 +2,7 @@ package com.mapbox.mapboxgl.annotations;
import com.mapbox.mapboxgl.views.MapView;
public abstract class Annotation {
public abstract class Annotation implements Comparable<Annotation> {
/**
* The annotation id
@@ -11,7 +10,7 @@ public abstract class Annotation {
* Internal C++ id is stored as unsigned int.
*/
protected long id = -1; // -1 unless added to a MapView
private MapView mapView;
protected MapView mapView;
float alpha = 1.0f;
boolean visible = true;
@@ -60,4 +59,35 @@ public abstract class Annotation {
// public void setZIndex(float zIndex) {
//
// }
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o instanceof Annotation) {
Annotation comp = (Annotation) o;
return id == comp.id;
}
return false;
}
@Override
public int compareTo(Annotation annotation) {
if (annotation == null) {
return -1;
}
if (id < annotation.getId()) {
return 1;
} else if (id > annotation.getId()) {
return -1;
}
// Equal
return 0;
}
}

View File

@@ -0,0 +1,199 @@
package com.mapbox.mapboxgl.annotations;
import android.content.Context;
import android.graphics.PointF;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.mapbox.mapboxgl.geometry.LatLng;
import com.mapbox.mapboxgl.views.MapView;
/**
* A tooltip view
*/
public class InfoWindow {
private Marker boundMarker;
private MapView mMapView;
private boolean mIsVisible;
protected View mView;
static int mTitleId = 0;
static int mDescriptionId = 0;
static int mSubDescriptionId = 0;
static int mImageId = 0;
public InfoWindow(int layoutResId, MapView mapView) {
mMapView = mapView;
mIsVisible = false;
Context context = mapView.getContext();
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(layoutResId, (ViewGroup) mapView, false);
if (mTitleId == 0) {
setResIds(mapView.getContext());
}
// default behavior: close it when clicking on the tooltip:
setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
close();
}
return true;
}
});
}
public InfoWindow(View view, MapView mapView) {
mMapView = mapView;
mIsVisible = false;
ViewGroup parent = (ViewGroup) mapView.getParent();
Context context = mapView.getContext();
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = view;
// default behavior: close it when clicking on the tooltip:
setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
close();
}
return true;
}
});
}
/**
* open the window at the specified position.
*
* @param object the graphical object on which is hooked the view
* @param position to place the window on the map
* @param offsetX (&offsetY) the offset of the view to the position, in pixels.
* This allows to offset the view from the object position.
* @return this infowindow
*/
public InfoWindow open(Marker object, LatLng position, int offsetX, int offsetY) {
onOpen(object);
MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT);
mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
PointF coords = mMapView.toScreenLocation(position);
double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite);
y = y * mMapView.getScreenDensity();
// Flip y coordinate as Android view origin is upper left corner
coords.y = mMapView.getHeight() - coords.y;
lp.leftMargin = (int) coords.x - (mView.getMeasuredWidth() / 2);
// Add y because it's a negative value
lp.topMargin = (int) coords.y - mView.getMeasuredHeight() + (int) y;
close(); //if it was already opened
mMapView.addView(mView, lp);
mIsVisible = true;
return this;
}
/**
* Close this InfoWindow if it is visible, otherwise don't do anything.
*
* @return this info window
*/
public InfoWindow close() {
if (mIsVisible) {
mIsVisible = false;
((ViewGroup) mView.getParent()).removeView(mView);
// this.boundMarker.blur();
setBoundMarker(null);
onClose();
}
return this;
}
/**
* Returns the Android view. This allows to set its content.
*
* @return the Android view
*/
public View getView() {
return mView;
}
/**
* Returns the mapView this InfoWindow is bound to
*
* @return the mapView
*/
public MapView getMapView() {
return mMapView;
}
/**
* Constructs the view that is displayed when the InfoWindow opens.
* This retrieves data from overlayItem and shows it in the tooltip.
*
* @param overlayItem the tapped overlay item
*/
public void onOpen(Marker overlayItem) {
String title = overlayItem.getTitle();
((TextView) mView.findViewById(mTitleId /*R.id.title*/)).setText(title);
String snippet = overlayItem.getSnippet();
((TextView) mView.findViewById(mDescriptionId /*R.id.description*/)).setText(snippet);
/*
//handle sub-description, hiding or showing the text view:
TextView subDescText = (TextView) mView.findViewById(mSubDescriptionId);
String subDesc = overlayItem.getSubDescription();
if ("".equals(subDesc)) {
subDescText.setVisibility(View.GONE);
} else {
subDescText.setText(subDesc);
subDescText.setVisibility(View.VISIBLE);
}
*/
}
public void onClose() {
//by default, do nothing
}
public InfoWindow setBoundMarker(Marker aBoundMarker) {
this.boundMarker = aBoundMarker;
return this;
}
public Marker getBoundMarker() {
return boundMarker;
}
/**
* Given a context, set the resource ids for the layout
* of the InfoWindow.
*
* @param context
*/
private static void setResIds(Context context) {
String packageName = context.getPackageName(); //get application package name
mTitleId = context.getResources().getIdentifier("id/infowindow_title", null, packageName);
mDescriptionId =
context.getResources().getIdentifier("id/infowindow_description", null, packageName);
mSubDescriptionId = context.getResources()
.getIdentifier("id/infowindow_subdescription", null, packageName);
mImageId = context.getResources().getIdentifier("id/infowindow_image", null, packageName);
}
/**
* Use to override default touch events handling on InfoWindow (ie, close automatically)
*
* @param listener New View.OnTouchListener to use
*/
public void setOnTouchListener(View.OnTouchListener listener) {
mView.setOnTouchListener(listener);
}
}

View File

@@ -0,0 +1,40 @@
package com.mapbox.mapboxgl.annotations;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
public class InfoWindowTipView extends View {
private Paint mPaint;
private Path mPath;
public InfoWindowTipView(Context context, AttributeSet attrs) {
super(context, attrs);
mPath = new Path();
this.mPaint = new Paint();
this.mPaint.setColor(Color.WHITE);
this.mPaint.setAntiAlias(true);
this.mPaint.setStrokeWidth(0.0f);
this.mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
int height = getMeasuredHeight();
int width = getMeasuredWidth();
mPath.rewind();
mPath.moveTo((width / 2) - height, 0);
mPath.lineTo((width / 2) + height, 0);
mPath.lineTo((width / 2), height);
mPath.lineTo((width / 2) - height, 0);
canvas.drawPath(mPath, this.mPaint);
}
}

View File

@@ -1,7 +1,9 @@
package com.mapbox.mapboxgl.annotations;
import android.graphics.Point;
import android.view.View;
import com.mapbox.mapboxgl.geometry.LatLng;
import com.mapbox.mapboxgl.views.R;
public class Marker extends Annotation {
@@ -16,10 +18,16 @@ public class Marker extends Annotation {
String snippet;
String sprite = "default_marker";
String title;
private InfoWindow infoWindow = null;
private boolean infoWindowShown = false;
public Marker() {}
/**
* Constructor
*/
public Marker() {
super();
}
/**
* If two markers have the same LatLng, they are equal.
@@ -41,6 +49,10 @@ public class Marker extends Annotation {
return alpha;
}
public Point getAnchor() {
return new Point((int)anchorU, (int)anchorV);
}
public float getAnchorU() {
return anchorU;
}
@@ -66,7 +78,7 @@ public class Marker extends Annotation {
}
public void hideInfoWindow() {
//TODO hideInfoWindow
infoWindow.close();
infoWindowShown = false;
}
@@ -82,33 +94,33 @@ public class Marker extends Annotation {
return infoWindowShown;
}
void setAnchor(float u, float v) {
public void setAnchor(float u, float v) {
this.anchorU = u;
this.anchorV = v;
}
void setDraggable(boolean draggable) {
public void setDraggable(boolean draggable) {
this.draggable = draggable;
}
void setFlat(boolean flat) {
public void setFlat(boolean flat) {
this.flat = flat;
}
void setInfoWindowAnchor(float u, float v) {
public void setInfoWindowAnchor(float u, float v) {
infoWindowAnchorU = u;
infoWindowAnchorV = v;
}
void setPosition(LatLng latlng) {
public void setPosition(LatLng latlng) {
this.position = position;
}
void setRotation(float rotation) {
public void setRotation(float rotation) {
this.rotation = rotation;
}
void setSnippet(String snippet) {
public void setSnippet(String snippet) {
this.snippet = snippet;
}
@@ -120,18 +132,55 @@ public class Marker extends Annotation {
*
* @param sprite
*/
void setSprite(String sprite) {
public void setSprite(String sprite) {
this.sprite = sprite;
}
void setTitle(String title) {
public void setTitle(String title) {
this.title = title;
}
void showInfoWindow() {
public void showInfoWindow() {
if (!isVisible() || mapView == null) {
return;
}
getInfoWindow().open(this, getPosition(), (int) anchorU, (int) anchorV);
getInfoWindow().setBoundMarker(this);
infoWindowShown = true;
}
/**
* Use to set a custom OnTouchListener for the InfoWindow.
* By default the InfoWindow will close on touch.
* @param listener Custom OnTouchListener
*/
public void setInfoWindowOnTouchListener(View.OnTouchListener listener) {
if (listener == null) {
return;
}
getInfoWindow().setOnTouchListener(listener);
}
/**
* Common internal InfoWindow initialization method
* @return InfoWindow for Marker
*/
private InfoWindow getInfoWindow() {
if (infoWindow == null) {
infoWindow = new InfoWindow(R.layout.infowindow, mapView);
}
return infoWindow;
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (!visible && infoWindowShown) {
hideInfoWindow();
}
}
// TODO Method in Google Maps Android API
// public int hashCode()

View File

@@ -1,6 +1,5 @@
package com.mapbox.mapboxgl.views;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -11,6 +10,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
@@ -27,7 +27,6 @@ import android.support.v4.content.ContextCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ScaleGestureDetectorCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.ScaleGestureDetector;
@@ -100,6 +99,8 @@ public class MapView extends FrameLayout implements LocationListener {
* Every annotation that has been added to the map.
*/
private List<Annotation> mAnnotations = new ArrayList<>();
private List<Annotation> mAnnotationsNearLastTap = new ArrayList<>();
private Annotation mSelectedAnnotation = null;
//
// Instance members
@@ -479,6 +480,9 @@ public class MapView extends FrameLayout implements LocationListener {
private void removeAnnotationsWithId(long annotationId){
for (Iterator<Annotation> iterator = mAnnotations.iterator(); iterator.hasNext();) {
Annotation annotation = iterator.next();
if (annotation instanceof Marker) {
((Marker)annotation).hideInfoWindow();
}
if (annotation.getId() == annotationId) {
iterator.remove();
}
@@ -486,6 +490,9 @@ public class MapView extends FrameLayout implements LocationListener {
}
public void removeAnnotation(Annotation annotation) {
if (annotation instanceof Marker) {
((Marker)annotation).hideInfoWindow();
}
long id = annotation.getId();
mNativeMapView.removeAnnotation(id);
mAnnotations.remove(annotation);
@@ -499,8 +506,12 @@ public class MapView extends FrameLayout implements LocationListener {
public void removeAnnotations() {
long[] ids = new long[mAnnotations.size()];
for(int i = 0; i < mAnnotations.size(); i++) {
long id = mAnnotations.get(i).getId();
Annotation annotation = mAnnotations.get(i);
long id = annotation.getId();
ids[i] = id;
if (annotation instanceof Marker) {
((Marker)annotation).hideInfoWindow();
}
}
mNativeMapView.removeAnnotations(ids);
mAnnotations.clear();
@@ -523,9 +534,6 @@ public class MapView extends FrameLayout implements LocationListener {
annotations.add(annotation);
}
}
for(int i = 0; i < annotations.size(); i++) {
Log.d(TAG, "tapped: " + Long.toString(annotations.get(i).getId()));
}
return annotations;
}
@@ -731,6 +739,18 @@ public class MapView extends FrameLayout implements LocationListener {
return new PointF(point.x * mScreenDensity, point.y * mScreenDensity);
}
public double getTopOffsetPixelsForAnnotationSymbol(@NonNull String symbolName) {
return mNativeMapView.getTopOffsetPixelsForAnnotationSymbol(symbolName);
}
/**
* Common Screen Density
* @return Screen Density
*/
public float getScreenDensity() {
return mScreenDensity;
}
//
// Lifecycle events
//
@@ -1026,32 +1046,104 @@ public class MapView extends FrameLayout implements LocationListener {
public boolean onSingleTapUp(MotionEvent e) {
// Cancel any animation
mNativeMapView.cancelTransitions();
// Select or deselect point annotations
PointF tapPoint = new PointF(e.getX(), e.getY());
float toleranceWidth = 60 * mScreenDensity;
float toleranceHeight = 80 * mScreenDensity;
PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3);
PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3);
LatLng sw = fromScreenLocation(bl);
LatLng ne = fromScreenLocation(tr);
BoundingBox bbox = new BoundingBox(ne, sw);
List<Annotation> annotations = getAnnotationsInBounds(bbox);
performClick();
return true;
}
// Called for single taps after a delay
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
// Open / Close InfoWindow
float x = e.getX();
float y = e.getY();
// flip y direction vertically to match core GL
y = getHeight() - y;
PointF tapPoint = new PointF(x, y);
float toleranceWidth = 40 * mScreenDensity;
float toleranceHeight = 60 * mScreenDensity;
RectF tapRect = new RectF(tapPoint.x - toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3,
tapPoint.x + toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3);
List<LatLng> corners = Arrays.asList(
fromScreenLocation(new PointF(tapRect.left, tapRect.bottom)),
fromScreenLocation(new PointF(tapRect.left, tapRect.top)),
fromScreenLocation(new PointF(tapRect.right, tapRect.top)),
fromScreenLocation(new PointF(tapRect.right, tapRect.bottom))
);
BoundingBox tapBounds = BoundingBox.fromLatLngs(corners);
List<Annotation> nearbyAnnotations = getAnnotationsInBounds(tapBounds);
long newSelectedAnnotationID = -1;
if (nearbyAnnotations.size() > 0) {
// there is at least one nearby annotation; select one
//
// first, sort for comparison and iteration
Collections.sort(nearbyAnnotations);
if (nearbyAnnotations == mAnnotationsNearLastTap)
{
// the selection candidates haven't changed; cycle through them
if (mSelectedAnnotation != null && (mSelectedAnnotation.getId() == mAnnotationsNearLastTap.get(mAnnotationsNearLastTap.size() - 1).getId()))
{
// the selected annotation is the last in the set; cycle back to the first
// note: this could be the selected annotation if only one in set
newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId();
}
else if (mSelectedAnnotation != null)
{
// otherwise increment the selection through the candidates
long currentID = mSelectedAnnotation.getId();
long result = mAnnotationsNearLastTap.indexOf(mSelectedAnnotation);
newSelectedAnnotationID = mAnnotationsNearLastTap.get((int) result + 1).getId();
}
else
{
// no current selection; select the first one
newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId();
}
}
else
{
// start tracking a new set of nearby annotations
mAnnotationsNearLastTap = nearbyAnnotations;
// select the first one
newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId();
}
} else {
// there are no nearby annotations; deselect if necessary
newSelectedAnnotationID = -1;
}
if (newSelectedAnnotationID >= 0) {
for (Annotation annotation : mAnnotations) {
if (annotation instanceof Marker) {
if (annotation.getId() == newSelectedAnnotationID) {
if (mSelectedAnnotation == null || annotation.getId() != mSelectedAnnotation.getId()) {
selectAnnotation(annotation);
}
break;
}
}
}
} else {
// deselect any selected annotation
if (mSelectedAnnotation != null) {
deselectAnnotation();
}
}
return true;
}
// Called for a long press
@@ -1485,7 +1577,7 @@ public class MapView extends FrameLayout implements LocationListener {
// Called for events that don't fit the other handlers
// such as mouse scroll events, mouse moves, joystick, trackpad
@Override @TargetApi(12)
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
// Mouse events
//if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18
@@ -1891,5 +1983,39 @@ public class MapView extends FrameLayout implements LocationListener {
mGpsMarker.setVisibility(View.INVISIBLE);
}
}
if (change.equals(MapChange.MapChangeRegionWillChange) || change.equals(MapChange.MapChangeRegionWillChangeAnimated)) {
deselectAnnotation();
}
}
private void selectAnnotation(Annotation annotation) {
if (annotation == null) {
return;
}
if (annotation == mSelectedAnnotation) {
return;
}
if (annotation instanceof Marker) {
// Need to deselect any currently selected annotation first
deselectAnnotation();
((Marker)annotation).showInfoWindow();
mSelectedAnnotation = annotation;
}
}
private void deselectAnnotation() {
if (mSelectedAnnotation != null && mSelectedAnnotation instanceof Marker) {
Marker marker = (Marker) mSelectedAnnotation;
if (marker.isInfoWindowShown()) {
marker.hideInfoWindow();
mSelectedAnnotation = null;
}
}
}
}

View File

@@ -3,7 +3,6 @@ package com.mapbox.mapboxgl.views;
import android.graphics.PointF;
import android.view.Surface;
import com.mapbox.mapboxgl.annotations.Annotation;
import com.mapbox.mapboxgl.annotations.Marker;
import com.mapbox.mapboxgl.annotations.Polygon;
import com.mapbox.mapboxgl.annotations.Polyline;
@@ -11,8 +10,6 @@ import com.mapbox.mapboxgl.geometry.BoundingBox;
import com.mapbox.mapboxgl.geometry.LatLng;
import com.mapbox.mapboxgl.geometry.LatLngZoom;
import com.mapbox.mapboxgl.geometry.ProjectedMeters;
import java.util.ArrayList;
import java.util.List;
// Class that wraps the native methods for convenience
@@ -424,6 +421,10 @@ class NativeMapView {
return nativeLatLngForPixel(mNativeMapViewPtr, pixel);
}
public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) {
return nativeGetTopOffsetPixelsForAnnotationSymbol(mNativeMapViewPtr, symbolName);
}
//
// Callbacks
//
@@ -606,4 +607,6 @@ class NativeMapView {
private native PointF nativePixelForLatLng(long nativeMapViewPtr, LatLng latLng);
private native LatLng nativeLatLngForPixel(long nativeMapViewPtr, PointF pixel);
private native double nativeGetTopOffsetPixelsForAnnotationSymbol(long nativeMapViewPtr, String symbolName);
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:background="@color/white"
android:padding="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/infowindow_title"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:textColor="@color/black"
android:textSize="18sp"
android:maxEms="17"
android:layout_gravity="left"
android:layout_weight="1"
android:text="@string/infoWindowTitle"/>
<TextView
android:id="@+id/infowindow_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="14sp"
android:maxEms="17"
android:text="@string/infoWindowDescription"/>
<TextView
android:id="@+id/infowindow_subdescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="12sp"
android:maxEms="17"
android:text="@string/infoWindowAddress"
android:visibility="gone"/>
</LinearLayout>
<com.mapbox.mapboxgl.annotations.InfoWindowTipView
android:layout_width="fill_parent"
android:layout_height="10dp"/>
</LinearLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
</resources>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="compassContentDescription">Map compass. Click to reset the map rotation to North.</string>
<string name="infoWindowTitle">Title</string>
<string name="infoWindowDescription">Description</string>
<string name="infoWindowAddress">Address</string>
</resources>