summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobrun <tobrun@mapbox.com>2017-07-07 12:28:19 +0200
committerGitHub <noreply@github.com>2017-07-07 12:28:19 +0200
commit2876db72f2d235a02e5670329c4f8dcb2a65a8ed (patch)
tree19b891e4feefb1ab818d9afeda115fda45103e6e
parent950ab7eb8aed1c288acea128771bd0c5b16eaeff (diff)
downloadqtlocation-mapboxgl-2876db72f2d235a02e5670329c4f8dcb2a65a8ed.tar.gz
Hit test Marker and MarkerViews (#9424)
* [android] - hit test Marker and MarkerViews * fixup
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java201
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java1
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml9
5 files changed, 141 insertions, 76 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java
index 7e7947047e..f3573c78ba 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java
@@ -1,10 +1,13 @@
package com.mapbox.mapboxsdk.maps;
+import android.graphics.Bitmap;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.LongSparseArray;
+import android.view.View;
import com.mapbox.mapboxsdk.annotations.Annotation;
import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions;
@@ -18,7 +21,6 @@ import com.mapbox.mapboxsdk.annotations.Polyline;
import com.mapbox.mapboxsdk.annotations.PolylineOptions;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -34,7 +36,6 @@ import java.util.List;
*/
class AnnotationManager {
- private final NativeMapView nativeMapView;
private final MapView mapView;
private final IconManager iconManager;
private final InfoWindowManager infoWindowManager = new InfoWindowManager();
@@ -52,7 +53,6 @@ class AnnotationManager {
AnnotationManager(NativeMapView view, MapView mapView, LongSparseArray<Annotation> annotationsArray,
MarkerViewManager markerViewManager, IconManager iconManager, Annotations annotations,
Markers markers, Polygons polygons, Polylines polylines) {
- this.nativeMapView = view;
this.mapView = mapView;
this.annotationsArray = annotationsArray;
this.markerViewManager = markerViewManager;
@@ -63,7 +63,7 @@ class AnnotationManager {
this.polylines = polylines;
if (view != null) {
// null checking needed for unit tests
- nativeMapView.addOnMapChangedListener(markerViewManager);
+ view.addOnMapChangedListener(markerViewManager);
}
}
@@ -329,84 +329,135 @@ class AnnotationManager {
// Click event
//
- boolean onTap(PointF tapPoint, float screenDensity) {
- float toleranceSides = 4 * screenDensity;
- float toleranceTopBottom = 10 * screenDensity;
- boolean handledDefaultClick = false;
-
- RectF tapRect = new RectF(tapPoint.x - iconManager.getAverageIconWidth() / 2 - toleranceSides,
- tapPoint.y - iconManager.getAverageIconHeight() / 2 - toleranceTopBottom,
- tapPoint.x + iconManager.getAverageIconWidth() / 2 + toleranceSides,
- tapPoint.y + iconManager.getAverageIconHeight() / 2 + toleranceTopBottom);
-
- List<Marker> nearbyMarkers = getMarkersInRect(tapRect);
- long newSelectedMarkerId = -1;
-
- // find a Marker that isn't selected yet
- if (nearbyMarkers.size() > 0) {
- Collections.sort(nearbyMarkers);
- for (Marker nearbyMarker : nearbyMarkers) {
- boolean found = false;
- for (Marker selectedMarker : selectedMarkers) {
- if (selectedMarker.equals(nearbyMarker)) {
- found = true;
- }
- }
- if (!found) {
- newSelectedMarkerId = nearbyMarker.getId();
- break;
+ boolean onTap(PointF tapPoint) {
+ MarkerHit markerHit = getMarkerHitFromTouchArea(tapPoint);
+ long markerId = new MarkerHitResolver(markerViewManager, mapboxMap.getProjection()).execute(markerHit);
+ return markerId >= 0 && isClickHandledForMarker(markerId);
+ }
+
+ private MarkerHit getMarkerHitFromTouchArea(PointF tapPoint) {
+ int averageIconWidthOffset = iconManager.getAverageIconWidth() / 2;
+ int averageIconHeightOffset = iconManager.getAverageIconHeight() / 2;
+ final RectF tapRect = new RectF(tapPoint.x - averageIconWidthOffset,
+ tapPoint.y - averageIconHeightOffset,
+ tapPoint.x + averageIconWidthOffset,
+ tapPoint.y + averageIconHeightOffset
+ );
+ return new MarkerHit(tapRect, getMarkersInRect(tapRect));
+ }
+
+ private boolean isClickHandledForMarker(long markerId) {
+ boolean handledDefaultClick;
+ Marker marker = (Marker) getAnnotation(markerId);
+ if (marker instanceof MarkerView) {
+ handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) marker);
+ } else {
+ handledDefaultClick = onClickMarker(marker);
+ }
+
+ if (!handledDefaultClick) {
+ setMarkerSelectionState(marker);
+ }
+ return true;
+ }
+
+ private boolean onClickMarker(Marker marker) {
+ return onMarkerClickListener != null && onMarkerClickListener.onMarkerClick(marker);
+ }
+
+ private void setMarkerSelectionState(Marker marker) {
+ if (!selectedMarkers.contains(marker)) {
+ selectMarker(marker);
+ } else {
+ deselectMarker(marker);
+ }
+ }
+
+ private static class MarkerHitResolver {
+
+ private final MarkerViewManager markerViewManager;
+ private final Projection projection;
+
+ private View view;
+ private Bitmap bitmap;
+ private PointF markerLocation;
+
+ private Rect hitRectView = new Rect();
+ private RectF hitRectMarker = new RectF();
+ private RectF highestSurfaceIntersection = new RectF();
+
+ private long closestMarkerId = -1;
+
+ MarkerHitResolver(@NonNull MarkerViewManager markerViewManager, @NonNull Projection projection) {
+ this.markerViewManager = markerViewManager;
+ this.projection = projection;
+ }
+
+ public long execute(MarkerHit markerHit) {
+ resolveForMarkers(markerHit);
+ return closestMarkerId;
+ }
+
+ private void resolveForMarkers(MarkerHit markerHit) {
+ for (Marker marker : markerHit.markers) {
+ if (marker instanceof MarkerView) {
+ resolveForMarkerView(markerHit, (MarkerView) marker);
+ } else {
+ resolveForMarker(markerHit, marker);
}
}
}
- // if unselected marker found
- if (newSelectedMarkerId >= 0) {
- List<Annotation> annotations = getAnnotations();
- int count = annotations.size();
- for (int i = 0; i < count; i++) {
- Annotation annotation = annotations.get(i);
- if (annotation instanceof Marker) {
- if (annotation.getId() == newSelectedMarkerId) {
- Marker marker = (Marker) annotation;
-
- if (marker instanceof MarkerView) {
- handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) marker);
- } else {
- if (onMarkerClickListener != null) {
- // end developer has provided a custom click listener
- handledDefaultClick = onMarkerClickListener.onMarkerClick(marker);
- }
- }
-
- if (!handledDefaultClick) {
- // only select marker if user didn't handle the click event themselves
- selectMarker(marker);
- }
-
- return true;
- }
- }
+ private void resolveForMarkerView(MarkerHit markerHit, MarkerView markerView) {
+ view = markerViewManager.getView(markerView);
+ if (view != null) {
+ view.getHitRect(hitRectView);
+ hitRectMarker = new RectF(hitRectView);
+ hitTestMarker(markerHit, markerView, hitRectMarker);
}
- } else if (nearbyMarkers.size() > 0) {
- // we didn't find an unselected marker, check if we can close an already open markers
- for (Marker nearbyMarker : nearbyMarkers) {
- for (Marker selectedMarker : selectedMarkers) {
- if (nearbyMarker.equals(selectedMarker)) {
- if (nearbyMarker instanceof MarkerView) {
- handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) nearbyMarker);
- } else if (onMarkerClickListener != null) {
- handledDefaultClick = onMarkerClickListener.onMarkerClick(nearbyMarker);
- }
-
- if (!handledDefaultClick) {
- // only deselect marker if user didn't handle the click event themselves
- deselectMarker(nearbyMarker);
- }
- return true;
- }
+ }
+
+ private void resolveForMarker(MarkerHit markerHit, Marker marker) {
+ markerLocation = projection.toScreenLocation(marker.getPosition());
+ bitmap = marker.getIcon().getBitmap();
+ hitRectMarker.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ hitRectMarker.offsetTo(
+ markerLocation.x - bitmap.getWidth() / 2,
+ markerLocation.y - bitmap.getHeight() / 2
+ );
+ hitTestMarker(markerHit, marker, hitRectMarker);
+ }
+
+ private void hitTestMarker(MarkerHit markerHit, Marker marker, RectF hitRectMarker) {
+ if (hitRectMarker.contains(markerHit.getTapPointX(), markerHit.getTapPointY())) {
+ hitRectMarker.intersect(markerHit.tapRect);
+ if (isRectangleHighestSurfaceIntersection(hitRectMarker)) {
+ highestSurfaceIntersection = new RectF(hitRectMarker);
+ closestMarkerId = marker.getId();
}
}
}
- return false;
+
+ private boolean isRectangleHighestSurfaceIntersection(RectF rectF) {
+ return rectF.width() * rectF.height() > highestSurfaceIntersection.width() * highestSurfaceIntersection.height();
+ }
+ }
+
+ private static class MarkerHit {
+ private final RectF tapRect;
+ private final List<Marker> markers;
+
+ MarkerHit(RectF tapRect, List<Marker> markers) {
+ this.tapRect = tapRect;
+ this.markers = markers;
+ }
+
+ float getTapPointX() {
+ return tapRect.centerX();
+ }
+
+ float getTapPointY() {
+ return tapRect.centerY();
+ }
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java
index 9f4171aee8..b5da5f59aa 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java
@@ -12,6 +12,8 @@ import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException;
import java.util.ArrayList;
import java.util.List;
+import timber.log.Timber;
+
/**
* Responsible for managing icons added to the Map.
* <p>
@@ -105,6 +107,8 @@ class IconManager {
int iconSize = icons.size() + 1;
averageIconHeight = averageIconHeight + (height - averageIconHeight) / iconSize;
averageIconWidth = averageIconWidth + (width - averageIconWidth) / iconSize;
+ Timber.e("OnUpdateAverageSizeIcon with: %s %s", width, height);
+ Timber.e("OnUpdateAverageSizeIcon now: %s %s", averageIconWidth, averageIconHeight);
}
private void loadIcon(Icon icon) {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
index 17eb506b97..330e09cf32 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
@@ -315,7 +315,7 @@ final class MapGestureDetector {
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
PointF tapPoint = new PointF(motionEvent.getX(), motionEvent.getY());
- boolean tapHandled = annotationManager.onTap(tapPoint, uiSettings.getPixelRatio());
+ boolean tapHandled = annotationManager.onTap(tapPoint);
if (!tapHandled) {
if (uiSettings.isDeselectMarkersOnTap()) {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java
index 306ad59b8d..ef8054d70e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java
@@ -261,6 +261,7 @@ class MarkerContainer implements Markers {
if (icon == null) {
icon = IconFactory.getInstance(mapView.getContext()).defaultMarkerView();
}
+ iconManager.loadIconForMarkerView(marker);
marker.setIcon(icon);
return marker;
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml
new file mode 100644
index 0000000000..cf8bfa24b5
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>