diff options
author | Tobrun <tobrun.van.nuland@gmail.com> | 2016-10-10 23:51:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-10-10 23:51:30 +0200 |
commit | e598bbfa7c29ad3a384e157b504c4509f1fcdbb5 (patch) | |
tree | 6dcdb0f9a358c0168b83097a97abb85b5f6aa729 | |
parent | 5891c7028dbc6ae6decb5a3b059fd48f4e700d44 (diff) |
Rotate markerview (#6640)
* [android] - MarkerView rotation bug, limit degrees from 0 to 360 while creating a MarkerView
* update MarkerView#setRotation to limit input from 0 - 360 degrees, calculates difference in angle and animates rotation by a certain amount instead of animating to a value. Exposed new AnimatorUtils#animateRotationTo. Integrated into MarkerViewManager. Cleanup some unused imports. Updated javadoc.
* updated MarkerViewActivity in test application to test for regressions.
* added tests to validate changes to MarkerView#setRotate
6 files changed, 212 insertions, 32 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java index a5c6397b6..d4eb390ab 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerViewOptions.java @@ -126,6 +126,12 @@ public abstract class BaseMarkerViewOptions<U extends MarkerView, T extends Base */ public T rotation(float rotation) { this.rotation = rotation; + while (this.rotation > 360) { + this.rotation -= 360; + } + while (this.rotation < 0) { + this.rotation += 360; + } return getThis(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java index b94b24025..b1008e290 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java @@ -1,18 +1,12 @@ package com.mapbox.mapboxsdk.annotations; -import android.animation.AnimatorSet; import android.graphics.Bitmap; -import android.graphics.PointF; import android.support.annotation.FloatRange; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.View; -import android.view.animation.AnimationUtils; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapboxMap; -import com.mapbox.mapboxsdk.utils.AnimatorUtils; /** * MarkerView is an annotation that shows an View at a geographical location. @@ -231,7 +225,10 @@ public class MarkerView extends Marker { } /** - * Set the rotation value of the MarkerView. + * Set the rotation value of the MarkerView in degrees. + * <p> + * Input will be limited to 0 - 360 degrees + * </p> * <p> * This will result in animating the rotation of the MarkerView using an rotation animator * from current value to the provided parameter value. @@ -240,9 +237,26 @@ public class MarkerView extends Marker { * @param rotation the rotation value to animate to */ public void setRotation(float rotation) { - this.rotation = rotation; + // limit to 0 - 360 degrees + float newRotation = rotation; + while (newRotation > 360) { + newRotation -= 360; + } + while (newRotation < 0) { + newRotation += 360; + } + + // calculate new direction + float diff = newRotation - this.rotation; + if (diff > 180.0f) { + diff -= 360.0f; + } else if (diff < -180.0f) { + diff += 360.f; + } + + this.rotation = newRotation; if (markerViewManager != null) { - markerViewManager.animateRotation(this, rotation); + markerViewManager.animateRotationBy(this, diff); } } @@ -339,7 +353,7 @@ public class MarkerView extends Marker { public void setMapboxMap(MapboxMap mapboxMap) { super.setMapboxMap(mapboxMap); - if(isFlat()) { + if (isFlat()) { // initial tilt value if MapboxMap is started with a tilt attribute tiltValue = (float) mapboxMap.getCameraPosition().tilt; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index 4742c9b66..fe1a48993 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -15,7 +15,6 @@ import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; -import com.mapbox.mapboxsdk.maps.Projection; import com.mapbox.mapboxsdk.utils.AnimatorUtils; import java.util.ArrayList; @@ -72,6 +71,19 @@ public class MarkerViewManager { } /** + * Animate a MarkerView with a given rotation. + * + * @param marker the MarkerView to rotate by + * @param rotation the rotation by value + */ + public void animateRotationBy(@NonNull MarkerView marker, float rotation) { + View convertView = markerViewMap.get(marker); + if (convertView != null) { + AnimatorUtils.rotateBy(convertView, rotation); + } + } + + /** * Animate a MarkerView to a given alpha value. * <p> * The {@link MarkerView} will be transformed from its current alpha value to the given value. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/AnimatorUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/AnimatorUtils.java index 495393c25..ab3b84104 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/AnimatorUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/AnimatorUtils.java @@ -7,6 +7,7 @@ import android.animation.ObjectAnimator; import android.support.annotation.AnimatorRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.view.View; public class AnimatorUtils { @@ -61,6 +62,17 @@ public class AnimatorUtils { rotateAnimator.start(); } + public static void rotateBy(@NonNull final View view, float rotationBy) { + view.setLayerType(View.LAYER_TYPE_HARDWARE, null); + view.animate().rotationBy(rotationBy).setInterpolator(new FastOutSlowInInterpolator()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + view.setLayerType(View.LAYER_TYPE_NONE, null); + } + }); + } + public static void alpha(@NonNull final View convertView, float alpha, @Nullable final OnAnimationEndListener listener) { convertView.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(convertView, View.ALPHA, convertView.getAlpha(), alpha); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/MarkerViewActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/MarkerViewActivity.java index ccce72713..8d678794c 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/MarkerViewActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/MarkerViewActivity.java @@ -41,24 +41,30 @@ import java.util.Random; public class MarkerViewActivity extends AppCompatActivity { + private static final LatLng[] LAT_LNGS = new LatLng[]{ + new LatLng(38.897424, -77.036508), + new LatLng(38.909698, -77.029642), + new LatLng(38.907227, -77.036530), + new LatLng(38.905607, -77.031916), + new LatLng(38.889441, -77.050134), + new LatLng(38.888000, -77.050000) //Slight overlap to show re-ordering on selection + }; + private MapboxMap mapboxMap; private MapView mapView; + // MarkerView location updates private MarkerView movingMarkerOne; private MarkerView movingMarkerTwo; private Random randomAnimator = new Random(); private Handler locationUpdateHandler = new Handler(); private Runnable moveMarkerRunnable = new MoveMarkerRunnable(); - private int rotation = 0; - private static final LatLng[] LAT_LNGS = new LatLng[]{ - new LatLng(38.897424, -77.036508), - new LatLng(38.909698, -77.029642), - new LatLng(38.907227, -77.036530), - new LatLng(38.905607, -77.031916), - new LatLng(38.889441, -77.050134), - new LatLng(38.888000, -77.050000) //Slight overlap to show re-ordering on selection - }; + // MarkerView rotate updates + private MarkerView rotateMarker; + private Handler rotateUpdateHandler = new Handler(); + private Runnable rotateMarkerRunnable = new RotateMarkerRunnable(); + private int rotation = 360; @Override protected void onCreate(Bundle savedInstanceState) { @@ -111,10 +117,13 @@ public class MarkerViewActivity extends AppCompatActivity { .position(new LatLng(38.902580, -77.050102)) ); - MarkerViewActivity.this.mapboxMap.addMarker(new TextMarkerViewOptions() + rotateMarker = MarkerViewActivity.this.mapboxMap.addMarker(new TextMarkerViewOptions() .text("A") + .rotation(rotation = 270) .position(new LatLng(38.889876, -77.008849)) ); + loopMarkerRotate(); + MarkerViewActivity.this.mapboxMap.addMarker(new TextMarkerViewOptions() .text("B") @@ -167,6 +176,10 @@ public class MarkerViewActivity extends AppCompatActivity { }); } + private void loopMarkerRotate() { + rotateUpdateHandler.postDelayed(rotateMarkerRunnable, 800); + } + @Override protected void onStart() { super.onStart(); @@ -183,27 +196,49 @@ public class MarkerViewActivity extends AppCompatActivity { locationUpdateHandler.removeCallbacks(moveMarkerRunnable); } + /** + * Updates the position of a Marker + */ private class MoveMarkerRunnable implements Runnable { @Override public void run() { int i = randomAnimator.nextInt(9); if (randomAnimator.nextInt() % 2 == 0) { movingMarkerOne.setPosition(CarLocation.CAR_0_LNGS[i]); - movingMarkerOne.setRotation(rotation = rotation + 45); } else { movingMarkerTwo.setPosition(CarLocation.CAR_1_LNGS[i]); - movingMarkerTwo.setRotation(rotation = rotation + 90); } loopMarkerMove(); } } + /** + * Updates the rotation of a Marker + */ + private class RotateMarkerRunnable implements Runnable { + + private final static int ROTATION_INCREASE_VALUE = 9; + + @Override + public void run() { + rotation -= ROTATION_INCREASE_VALUE; + if (rotation >= 0) { + rotation += 360; + } + rotateMarker.setRotation(rotation); + loopMarkerRotate(); + } + } + + /** + * Adapts a MarkerView to display an abbreviated name in a TextView and a flag in an ImageView. + */ private static class CountryAdapter extends MapboxMap.MarkerViewAdapter<CountryMarkerView> { private LayoutInflater inflater; private MapboxMap mapboxMap; - public CountryAdapter(@NonNull Context context, @NonNull MapboxMap mapboxMap) { + CountryAdapter(@NonNull Context context, @NonNull MapboxMap mapboxMap) { super(context); this.inflater = LayoutInflater.from(context); this.mapboxMap = mapboxMap; @@ -229,7 +264,7 @@ public class MarkerViewActivity extends AppCompatActivity { @Override public boolean onSelect( - @NonNull final CountryMarkerView marker, @NonNull final View convertView, boolean reselectionForViewReuse) { + @NonNull final CountryMarkerView marker, @NonNull final View convertView, boolean reselectionForViewReuse) { convertView.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(convertView, View.ROTATION, 0, 360); rotateAnimator.setDuration(reselectionForViewReuse ? 0 : 350); @@ -268,13 +303,15 @@ public class MarkerViewActivity extends AppCompatActivity { } } - + /** + * Adapts a MarkerView to display text in a TextView. + */ private static class TextAdapter extends MapboxMap.MarkerViewAdapter<TextMarkerView> { private LayoutInflater inflater; private MapboxMap mapboxMap; - public TextAdapter(@NonNull Context context, @NonNull MapboxMap mapboxMap) { + TextAdapter(@NonNull Context context, @NonNull MapboxMap mapboxMap) { super(context); this.inflater = LayoutInflater.from(context); this.mapboxMap = mapboxMap; @@ -298,7 +335,7 @@ public class MarkerViewActivity extends AppCompatActivity { @Override public boolean onSelect( - @NonNull final TextMarkerView marker, @NonNull final View convertView, boolean reselectionForViewReuse) { + @NonNull final TextMarkerView marker, @NonNull final View convertView, boolean reselectionForViewReuse) { animateGrow(marker, convertView, 0); // false indicates that we are calling selectMarker after our animation ourselves @@ -409,7 +446,7 @@ public class MarkerViewActivity extends AppCompatActivity { private static class CarLocation { - public static LatLng[] CAR_0_LNGS = new LatLng[]{ + static LatLng[] CAR_0_LNGS = new LatLng[]{ new LatLng(38.92334425495122, -77.0533673443786), new LatLng(38.9234737236897, -77.05389484528261), new LatLng(38.9257094658146, -76.98819752280579), @@ -421,7 +458,7 @@ public class MarkerViewActivity extends AppCompatActivity { new LatLng(38.862930274733635, -76.99647808241964) }; - public static LatLng[] CAR_1_LNGS = new LatLng[]{ + static LatLng[] CAR_1_LNGS = new LatLng[]{ new LatLng(38.94237975070426, -76.98324549005675), new LatLng(38.941520236084486, -76.98234257804742), new LatLng(38.85972219720714, -76.98955808483929), @@ -432,6 +469,5 @@ public class MarkerViewActivity extends AppCompatActivity { new LatLng(38.882869724926245, -77.02992539231113), new LatLng(38.9371988177896, -76.97786740676564) }; - } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java index e6c30f4aa..6cef1898b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerViewTest.java @@ -4,18 +4,36 @@ import android.os.Parcelable; import com.mapbox.mapboxsdk.exceptions.InvalidMarkerPositionException; import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.utils.MockParcel; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class MarkerViewTest { + @Mock + MapboxMap mapboxMap; + + @Mock + MarkerViewManager markerViewManager; + + @Before + public void beforeTest() { + MockitoAnnotations.initMocks(this); + } + @Test public void testSanity() { MarkerViewOptions markerOptions = new MarkerViewOptions(); @@ -29,7 +47,7 @@ public class MarkerViewTest { } @Test(expected = InvalidMarkerPositionException.class) - public void testInvalidMarker(){ + public void testInvalidMarker() { new MarkerViewOptions().getMarker(); } @@ -111,6 +129,88 @@ public class MarkerViewTest { } @Test + public void testRotationAboveMax() { + MarkerViewOptions markerOptions = new MarkerViewOptions().rotation(390).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getRotation(), 30, 0); + } + + @Test + public void testRotationBelowMin() { + MarkerViewOptions markerOptions = new MarkerViewOptions().rotation(-10).position(new LatLng()); + MarkerView marker = markerOptions.getMarker(); + assertEquals(marker.getRotation(), 350, 0); + } + + @Test + public void testRotationUpdatePositive() { + float startRotation = 45; + float endRotation = 180; + float animationValue = 135; + + // allow calls to our mock + when(mapboxMap.getMarkerViewManager()).thenReturn(markerViewManager); + + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).rotation(startRotation); + MarkerView marker = markerOptions.getMarker(); + marker.setMapboxMap(mapboxMap); + + marker.setRotation(endRotation); + verify(markerViewManager, times(1)).animateRotationBy(marker, animationValue); + } + + @Test + public void testRotationUpdateNegative() { + float startRotation = 10; + float endRotation = 270; + float animationValue = -100; + + // allow calls to our mock + when(mapboxMap.getMarkerViewManager()).thenReturn(markerViewManager); + + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).rotation(startRotation); + MarkerView marker = markerOptions.getMarker(); + marker.setMapboxMap(mapboxMap); + + marker.setRotation(endRotation); + verify(markerViewManager, times(1)).animateRotationBy(marker, animationValue); + } + + @Test + public void testRotationUpdateMax() { + float startRotation = 359; + float endRotation = 0; + float animationValue = 1; + + // allow calls to our mock + when(mapboxMap.getMarkerViewManager()).thenReturn(markerViewManager); + + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).rotation(startRotation); + MarkerView marker = markerOptions.getMarker(); + marker.setMapboxMap(mapboxMap); + + marker.setRotation(endRotation); + verify(markerViewManager, times(1)).animateRotationBy(marker, animationValue); + } + + @Test + public void testRotationUpdateMin() { + float startRotation = 0; + float endRotation = 359; + float animationValue = -1; + + // allow calls to our mock + when(mapboxMap.getMarkerViewManager()).thenReturn(markerViewManager); + + MarkerViewOptions markerOptions = new MarkerViewOptions().position(new LatLng()).rotation(startRotation); + MarkerView marker = markerOptions.getMarker(); + marker.setMapboxMap(mapboxMap); + + marker.setRotation(endRotation); + verify(markerViewManager, times(1)).animateRotationBy(marker, animationValue); + } + + @Test public void testVisible() { boolean visible = false; MarkerViewOptions markerOptions = new MarkerViewOptions().visible(visible).position(new LatLng()); |