[android] - port animated markers activity to symbol layer

This commit is contained in:
Tobrun
2017-12-20 16:44:21 +01:00
committed by Tobrun
parent fcf5fa6bbb
commit dfb9b26e67
7 changed files with 459 additions and 293 deletions

View File

@@ -69,12 +69,12 @@
android:value=".activity.FeatureOverviewActivity"/>
</activity>
<activity
android:name=".activity.annotation.AnimatedMarkerActivity"
android:description="@string/description_animated_marker"
android:label="@string/activity_animated_marker">
android:name=".activity.annotation.AnimatedSymbolLayerActivity"
android:description="@string/description_animated_symbollayer"
android:label="@string/activity_animated_symbollayer">
<meta-data
android:name="@string/category"
android:value="@string/category_annotation"/>
android:value="@string/category_style"/>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>

View File

@@ -1,283 +0,0 @@
package com.mapbox.mapboxsdk.testapp.activity.annotation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.annotations.Icon;
import com.mapbox.mapboxsdk.annotations.IconFactory;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.MarkerView;
import com.mapbox.mapboxsdk.annotations.MarkerViewManager;
import com.mapbox.mapboxsdk.annotations.MarkerViewOptions;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.utils.IconUtils;
import com.mapbox.turf.TurfMeasurement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Test activity showcasing animating MarkerViews.
*/
public class AnimatedMarkerActivity extends AppCompatActivity {
private MapView mapView;
private MapboxMap mapboxMap;
private LatLng dupontCircle = new LatLng(38.90962, -77.04341);
private Marker passengerMarker = null;
private MarkerView carMarker = null;
private Runnable animationRunnable;
private List<MarkerView> markerViews = new ArrayList<>();
private boolean stopped;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animated_marker);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap -> {
AnimatedMarkerActivity.this.mapboxMap = mapboxMap;
setupMap();
animationRunnable = () -> {
for (int i = 0; i < 10; i++) {
addRandomCar();
}
addPassenger();
addMainCar();
};
mapView.post(animationRunnable);
});
}
private void setupMap() {
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(dupontCircle)
.zoom(15)
.build();
mapboxMap.setCameraPosition(cameraPosition);
}
private void addPassenger() {
if (isActivityStopped()) {
return;
}
LatLng randomLatLng = getLatLngInBounds();
if (passengerMarker == null) {
Icon icon = IconUtils.drawableToIcon(this, R.drawable.ic_directions_run_black,
ResourcesCompat.getColor(getResources(), R.color.blueAccent, getTheme()));
passengerMarker = mapboxMap.addMarker(new MarkerViewOptions()
.position(randomLatLng)
.icon(icon));
} else {
passengerMarker.setPosition(randomLatLng);
}
}
private void addMainCar() {
if (isActivityStopped()) {
return;
}
LatLng randomLatLng = getLatLngInBounds();
if (carMarker == null) {
carMarker = createCarMarker(randomLatLng, R.drawable.ic_taxi_top,
markerView -> {
// Make sure the car marker is selected so that it's always brought to the front (#5285)
mapboxMap.selectMarker(carMarker);
animateMoveToPassenger(carMarker);
});
markerViews.add(carMarker);
} else {
carMarker.setPosition(randomLatLng);
}
}
private void animateMoveToPassenger(final MarkerView car) {
if (isActivityStopped()) {
return;
}
ValueAnimator animator = animateMoveMarker(car, passengerMarker.getPosition());
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
addPassenger();
animateMoveToPassenger(car);
}
});
}
protected void addRandomCar() {
markerViews.add(createCarMarker(getLatLngInBounds(), R.drawable.ic_car_top,
markerView -> randomlyMoveMarker(markerView)));
}
private void randomlyMoveMarker(final MarkerView marker) {
if (isActivityStopped()) {
return;
}
ValueAnimator animator = animateMoveMarker(marker, getLatLngInBounds());
// Add listener to restart animation on end
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
randomlyMoveMarker(marker);
}
});
}
private ValueAnimator animateMoveMarker(final MarkerView marker, LatLng to) {
marker.setRotation((float) getBearing(marker.getPosition(), to));
final ValueAnimator markerAnimator = ObjectAnimator.ofObject(
marker, "position", new LatLngEvaluator(), marker.getPosition(), to);
markerAnimator.setDuration((long) (10 * marker.getPosition().distanceTo(to)));
markerAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// Start
markerAnimator.start();
return markerAnimator;
}
private MarkerView createCarMarker(LatLng start, @DrawableRes int carResource,
MarkerViewManager.OnMarkerViewAddedListener listener) {
Icon icon = IconFactory.getInstance(AnimatedMarkerActivity.this)
.fromResource(carResource);
// View Markers
return mapboxMap.addMarker(new MarkerViewOptions()
.position(start)
.icon(icon), listener);
// GL Markers
// return mapboxMap.addMarker(new MarkerOptions()
// .position(start)
// .icon(icon));
}
private LatLng getLatLngInBounds() {
LatLngBounds bounds = mapboxMap.getProjection().getVisibleRegion().latLngBounds;
Random generator = new Random();
double randomLat = bounds.getLatSouth() + generator.nextDouble()
* (bounds.getLatNorth() - bounds.getLatSouth());
double randomLon = bounds.getLonWest() + generator.nextDouble()
* (bounds.getLonEast() - bounds.getLonWest());
return new LatLng(randomLat, randomLon);
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
stopped = true;
// Stop ongoing animations, prevent memory leaks
if (mapboxMap != null) {
MarkerViewManager markerViewManager = mapboxMap.getMarkerViewManager();
for (MarkerView markerView : markerViews) {
View view = markerViewManager.getView(markerView);
if (view != null) {
view.animate().cancel();
}
}
}
// onStop
mapView.onStop();
mapView.removeCallbacks(animationRunnable);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
/**
* Evaluator for LatLng pairs
*/
private static class LatLngEvaluator implements TypeEvaluator<LatLng> {
private LatLng latLng = new LatLng();
@Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
latLng.setLatitude(startValue.getLatitude()
+ ((endValue.getLatitude() - startValue.getLatitude()) * fraction));
latLng.setLongitude(startValue.getLongitude()
+ ((endValue.getLongitude() - startValue.getLongitude()) * fraction));
return latLng;
}
}
private double getBearing(LatLng from, LatLng to) {
return TurfMeasurement.bearing(
Point.fromLngLat(from.getLongitude(), from.getLatitude()),
Point.fromLngLat(to.getLongitude(), to.getLatitude())
);
}
private boolean isActivityStopped() {
return stopped;
}
}

View File

@@ -0,0 +1,449 @@
package com.mapbox.mapboxsdk.testapp.activity.annotation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import com.google.gson.JsonObject;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.functions.stops.Stops;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.turf.TurfMeasurement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static com.mapbox.mapboxsdk.style.functions.Function.property;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconRotate;
/**
* Test activity showcasing animating MarkerViews.
*/
public class AnimatedSymbolLayerActivity extends AppCompatActivity {
private static final String PASSENGER = "passenger";
private static final String PASSENGER_LAYER = "passenger-layer";
private static final String PASSENGER_SOURCE = "passenger-source";
private static final String TAXI = "taxi";
private static final String TAXI_LAYER = "taxi-layer";
private static final String TAXI_SOURCE = "taxi-source";
private static final String RANDOM_CAR_LAYER = "random-car-layer";
private static final String RANDOM_CAR_SOURCE = "random-car-source";
private static final String RANDOM_CAR_IMAGE_ID = "random-car";
private static final String PROPERTY_BEARING = "bearing";
private static final String WATERWAY_LAYER_ID = "waterway-label";
private static final int DURATION_RANDOM_MAX = 1500;
private static final int DURATION_BASE = 3000;
private final Random random = new Random();
private MapView mapView;
private MapboxMap mapboxMap;
private List<Car> randomCars = new ArrayList<>();
private GeoJsonSource randomCarSource;
private Car taxi;
private GeoJsonSource taxiSource;
private LatLng passenger;
private List<Animator> animators = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animated_marker);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(mapboxMap -> {
AnimatedSymbolLayerActivity.this.mapboxMap = mapboxMap;
setupCars();
animateRandomRoutes();
animateTaxi();
});
}
private void setupCars() {
addRandomCars();
addPassenger();
addMainCar();
}
private void animateRandomRoutes() {
final Car longestDrive = getLongestDrive();
final Random random = new Random();
for (final Car car : randomCars) {
final boolean isLongestDrive = longestDrive.equals(car);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), car.current, car.next);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private LatLng latLng;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
latLng = (LatLng) animation.getAnimatedValue();
car.current = latLng;
if (isLongestDrive) {
updateRandomCarSource();
}
}
});
if (isLongestDrive) {
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
updateRandomDestinations();
animateRandomRoutes();
}
});
}
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
car.feature.properties().addProperty("bearing", Car.getBearing(car.current, car.next));
}
});
int offset = random.nextInt(2) == 0 ? 0 : random.nextInt(1000) + 250;
valueAnimator.setStartDelay(offset);
valueAnimator.setDuration(car.duration - offset);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.start();
animators.add(valueAnimator);
}
}
private void animateTaxi() {
ValueAnimator valueAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), taxi.current, taxi.next);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private LatLng latLng;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
latLng = (LatLng) animation.getAnimatedValue();
taxi.current = latLng;
updateTaxiSource();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
updatePassenger();
animateTaxi();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
taxi.feature.properties().addProperty("bearing", Car.getBearing(taxi.current, taxi.next));
}
});
valueAnimator.setDuration((long) (7 * taxi.current.distanceTo(taxi.next)));
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.start();
animators.add(valueAnimator);
}
private void updatePassenger() {
passenger = getLatLngInBounds();
updatePassengerSource();
taxi.setNext(passenger);
}
private void updatePassengerSource() {
GeoJsonSource source = mapboxMap.getSourceAs(PASSENGER_SOURCE);
FeatureCollection featureCollection = FeatureCollection.fromFeatures(new Feature[] {
Feature.fromGeometry(
Point.fromLngLat(
passenger.getLongitude(),
passenger.getLatitude()
)
)
});
source.setGeoJson(featureCollection);
}
private void updateTaxiSource() {
taxi.updateFeature();
taxiSource.setGeoJson(taxi.feature);
}
private void updateRandomDestinations() {
for (Car randomCar : randomCars) {
randomCar.setNext(getLatLngInBounds());
}
}
private Car getLongestDrive() {
Car longestDrive = null;
for (Car randomCar : randomCars) {
if (longestDrive == null) {
longestDrive = randomCar;
} else if (longestDrive.duration < randomCar.duration) {
longestDrive = randomCar;
}
}
return longestDrive;
}
private void updateRandomCarSource() {
for (Car randomCarsRoute : randomCars) {
randomCarsRoute.updateFeature();
}
randomCarSource.setGeoJson(featuresFromRoutes());
}
private FeatureCollection featuresFromRoutes() {
List<Feature> features = new ArrayList<>();
for (Car randomCarsRoute : randomCars) {
features.add(randomCarsRoute.feature);
}
return FeatureCollection.fromFeatures(features);
}
private long getDuration() {
return random.nextInt(DURATION_RANDOM_MAX) + DURATION_BASE;
}
private void addRandomCars() {
LatLng latLng;
LatLng next;
for (int i = 0; i < 10; i++) {
latLng = getLatLngInBounds();
next = getLatLngInBounds();
JsonObject properties = new JsonObject();
properties.addProperty(PROPERTY_BEARING, Car.getBearing(latLng, next));
Feature feature = Feature.fromGeometry(
Point.fromLngLat(
latLng.getLongitude(),
latLng.getLatitude()
), properties);
randomCars.add(
new Car(feature, next, getDuration())
);
}
randomCarSource = new GeoJsonSource(RANDOM_CAR_SOURCE, featuresFromRoutes());
mapboxMap.addSource(randomCarSource);
mapboxMap.addImage(RANDOM_CAR_IMAGE_ID,
((BitmapDrawable) getResources().getDrawable(R.drawable.ic_car_top)).getBitmap());
SymbolLayer symbolLayer = new SymbolLayer(RANDOM_CAR_LAYER, RANDOM_CAR_SOURCE);
symbolLayer.withProperties(
iconImage(RANDOM_CAR_IMAGE_ID),
iconAllowOverlap(true),
iconRotate(
property(
PROPERTY_BEARING,
Stops.<Float>identity()
)
),
iconIgnorePlacement(true)
);
mapboxMap.addLayerBelow(symbolLayer, WATERWAY_LAYER_ID);
}
private void addPassenger() {
passenger = getLatLngInBounds();
FeatureCollection featureCollection = FeatureCollection.fromFeatures(new Feature[] {
Feature.fromGeometry(
Point.fromLngLat(
passenger.getLongitude(),
passenger.getLatitude()
)
)
});
mapboxMap.addImage(PASSENGER,
((BitmapDrawable) getResources().getDrawable(R.drawable.icon_burned)).getBitmap());
GeoJsonSource geoJsonSource = new GeoJsonSource(PASSENGER_SOURCE, featureCollection);
mapboxMap.addSource(geoJsonSource);
SymbolLayer symbolLayer = new SymbolLayer(PASSENGER_LAYER, PASSENGER_SOURCE);
symbolLayer.withProperties(
iconImage(PASSENGER),
iconIgnorePlacement(true),
iconAllowOverlap(true)
);
mapboxMap.addLayerBelow(symbolLayer, RANDOM_CAR_LAYER);
}
private void addMainCar() {
LatLng latLng = getLatLngInBounds();
JsonObject properties = new JsonObject();
properties.addProperty(PROPERTY_BEARING, Car.getBearing(latLng, passenger));
Feature feature = Feature.fromGeometry(
Point.fromLngLat(
latLng.getLongitude(),
latLng.getLatitude()), properties);
FeatureCollection featureCollection = FeatureCollection.fromFeatures(new Feature[] {feature});
taxi = new Car(feature, passenger, getDuration());
mapboxMap.addImage(TAXI,
((BitmapDrawable) getResources().getDrawable(R.drawable.ic_taxi_top)).getBitmap());
taxiSource = new GeoJsonSource(TAXI_SOURCE, featureCollection);
mapboxMap.addSource(taxiSource);
SymbolLayer symbolLayer = new SymbolLayer(TAXI_LAYER, TAXI_SOURCE);
symbolLayer.withProperties(
iconImage(TAXI),
iconRotate(
property(
PROPERTY_BEARING,
Stops.<Float>identity()
)
),
iconAllowOverlap(true),
iconIgnorePlacement(true)
);
mapboxMap.addLayer(symbolLayer);
}
private LatLng getLatLngInBounds() {
LatLngBounds bounds = mapboxMap.getProjection().getVisibleRegion().latLngBounds;
Random generator = new Random();
double randomLat = bounds.getLatSouth() + generator.nextDouble()
* (bounds.getLatNorth() - bounds.getLatSouth());
double randomLon = bounds.getLonWest() + generator.nextDouble()
* (bounds.getLonEast() - bounds.getLonWest());
return new LatLng(randomLat, randomLon);
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
for (Animator animator : animators) {
if (animator != null) {
animator.removeAllListeners();
animator.cancel();
}
}
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
/**
* Evaluator for LatLng pairs
*/
private static class LatLngEvaluator implements TypeEvaluator<LatLng> {
private LatLng latLng = new LatLng();
@Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
latLng.setLatitude(startValue.getLatitude()
+ ((endValue.getLatitude() - startValue.getLatitude()) * fraction));
latLng.setLongitude(startValue.getLongitude()
+ ((endValue.getLongitude() - startValue.getLongitude()) * fraction));
return latLng;
}
}
private static class Car {
private Feature feature;
private LatLng next;
private LatLng current;
private long duration;
Car(Feature feature, LatLng next, long duration) {
this.feature = feature;
Point point = ((Point) feature.geometry());
this.current = new LatLng(point.latitude(), point.longitude());
this.duration = duration;
this.next = next;
}
void setNext(LatLng next) {
this.next = next;
}
void updateFeature() {
feature = Feature.fromGeometry(Point.fromLngLat(
current.getLongitude(),
current.getLatitude())
);
feature.properties().addProperty("bearing", getBearing(current, next));
}
private static float getBearing(LatLng from, LatLng to) {
return (float) TurfMeasurement.bearing(
Point.fromLngLat(from.getLongitude(), from.getLatitude()),
Point.fromLngLat(to.getLongitude(), to.getLatitude())
);
}
}
}

View File

@@ -10,9 +10,9 @@
android:id="@id/mapView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:mapbox_cameraTargetLat="51.502615"
app:mapbox_cameraTargetLng="4.972326"
app:mapbox_cameraZoom="6"
app:mapbox_cameraTargetLat="38.90962"
app:mapbox_cameraTargetLng="-77.04341"
app:mapbox_cameraZoom="15"
app:mapbox_styleUrl="@string/mapbox_style_light"/>
</LinearLayout>

View File

@@ -24,7 +24,7 @@
<string name="description_offline">Offline Map example</string>
<string name="description_update_metadata">Update metadata example</string>
<string name="description_offline_region_delete">Delete region example</string>
<string name="description_animated_marker">Animate the position change of a marker</string>
<string name="description_animated_symbollayer">Animate the position change of a symbol layer</string>
<string name="description_polyline">Add a polyline to a map</string>
<string name="description_polygon">Add a polygon to a map</string>
<string name="description_scroll_by">Scroll with pixels in x,y direction</string>

View File

@@ -4,7 +4,7 @@
<string name="activity_map_fragment">Map Fragment</string>
<string name="activity_multimap">Multiple Maps on Screen</string>
<string name="activity_add_bulk_markers">Add Markers In Bulk</string>
<string name="activity_animated_marker">Animated Markers</string>
<string name="activity_animated_symbollayer">Animated SymbolLayer</string>
<string name="activity_dynamic_marker">Dynamic Marker</string>
<string name="activity_polyline">Polyline</string>
<string name="activity_polygon">Polygon</string>

View File

@@ -18,7 +18,7 @@
"LocationPickerActivity",
"GeoJsonClusteringActivity",
"RuntimeStyleTestActivity",
"AnimatedMarkerActivity",
"AnimatedSymbolLayerActivity",
"ViewPagerActivity",
"MapFragmentActivity",
"SupportMapFragmentActivity",