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
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -71,6 +70,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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -41,16 +41,6 @@ import java.util.Random;
|
||||
|
||||
public class MarkerViewActivity extends AppCompatActivity {
|
||||
|
||||
private MapboxMap mapboxMap;
|
||||
private MapView mapView;
|
||||
|
||||
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),
|
||||
@@ -60,6 +50,22 @@ public class MarkerViewActivity extends AppCompatActivity {
|
||||
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();
|
||||
|
||||
// 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) {
|
||||
super.onCreate(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)
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -110,6 +128,88 @@ public class MarkerViewTest {
|
||||
assertEquals("rotation should match ", rotation, marker.getRotation(), 0);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
Reference in New Issue
Block a user