Skip to main content

Animated icon movement

A newer version of the Maps SDK is available
This page uses v9.7.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.3.1, in the Maps SDK documentation.
Note

This example is a part of the Mapbox Android Demo app. You can find the values for all referenced resources in the res directory. For example, see res/values/activity_strings.xml for R.string.* references used in this example.

activity_animated_pin_drop
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/res-auto"
xmlns:mapbox="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".examples.labs.ValueAnimatorIconAnimationActivity">


<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
mapbox:mapbox_cameraTargetLat="0.282219"
mapbox:mapbox_cameraTargetLng="117.226205"
mapbox:mapbox_cameraTilt="55"
mapbox:mapbox_cameraZoom="4"
mapbox:mapbox_uiRotateGestures="false" />

<com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/interpolator_selection_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
fab:fab_addButtonColorNormal="@color/mapboxRed"
fab:fab_addButtonColorPressed="@color/mapboxWhite"
fab:fab_addButtonPlusIconColor="@color/mapboxWhite"
fab:fab_labelStyle="@style/menu_labels_style"
mapbox:layout_constraintBottom_toBottomOf="parent"
mapbox:layout_constraintEnd_toEndOf="parent">

<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_bounce_interpolator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/mapboxBlue"
fab:fab_colorPressed="@color/mapboxWhite"
fab:fab_size="mini"
fab:fab_title="@string/bounce_interpolator" />

<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_linear_interpolator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/mapboxGreen"
fab:fab_colorPressed="@color/mapboxWhite"
fab:fab_size="mini"
fab:fab_title="@string/linear_interpolator" />

<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_accelerate_interpolator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/mapboxRed"
fab:fab_colorPressed="@color/mapboxWhite"
fab:fab_size="mini"
fab:fab_title="@string/accelerate_interpolator" />

<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_decelerate_interpolator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_colorNormal="@color/mapboxYellow"
fab:fab_colorPressed="@color/mapboxWhite"
fab:fab_size="mini"
fab:fab_title="@string/decelerate_interpolator" />

</com.getbase.floatingactionbutton.FloatingActionsMenu>


</androidx.constraintlayout.widget.ConstraintLayout>
ValueAnimatorIconAnimationActivity.java
package com.mapbox.mapboxandroiddemo.examples.labs;

import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;

import com.getbase.floatingactionbutton.FloatingActionButton;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxandroiddemo.R;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.utils.BitmapUtils;

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.iconOffset;

/**
* Combine SymbolLayer icons with the Android system's ValueAnimator and interpolator
* animation for a fun pin drop effect. The interpolator movement can also be used with other
* types of map layers, such as a LineLayer or CircleLayer.
* <p>
* More info about https://developer.android.com/reference/android/view/animation/Interpolator
*/
public class ValueAnimatorIconAnimationActivity extends AppCompatActivity implements
OnMapReadyCallback, MapView.OnDidFinishRenderingMapListener {

private static final String ICON_ID = "red-pin-icon-id";

// This float's actual value will depend on the height of the SymbolLayer icon
private static final float DEFAULT_DESIRED_ICON_OFFSET = -16;
private static final float STARTING_DROP_HEIGHT = -100;
private static final long DROP_SPEED_MILLISECONDS = 1200;
private static final String SYMBOL_LAYER_ID = "symbol-layer-id";
private MapView mapView;
private SymbolLayer pinSymbolLayer;
private Style style;
private TimeInterpolator currentSelectedTimeInterpolator = new BounceInterpolator();
private ValueAnimator animator;
private boolean firstRunThrough = true;
private boolean animationHasStarted;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Mapbox access token is configured here. This needs to be called either in your application
// object or in the same activity which contains the mapview.
Mapbox.getInstance(this, getString(R.string.access_token));

// This contains the MapView in XML and needs to be called after the access token is configured.
setContentView(R.layout.activity_animated_pin_drop);

// Initialize the map view
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
}

@Override
public void onMapReady(@NonNull final MapboxMap mapboxMap) {
mapboxMap.setStyle(new Style.Builder().fromUri(Style.LIGHT)
// Add GeoJsonSource with random Features to the map.
.withSource(new GeoJsonSource("source-id",
FeatureCollection.fromFeatures(new Feature[] {
Feature.fromGeometry(Point.fromLngLat(
119.86083984375,
-1.834403324493515)),
Feature.fromGeometry(Point.fromLngLat(
116.06637239456177,
5.970619502704659)),
Feature.fromGeometry(Point.fromLngLat(
114.58740234375,
4.54357027937176)),
Feature.fromGeometry(Point.fromLngLat(
118.19091796875,
5.134714634014467)),
Feature.fromGeometry(Point.fromLngLat(
110.36865234374999,
1.4500404973608074)),
Feature.fromGeometry(Point.fromLngLat(
109.40185546874999,
0.3076157096439005)),
Feature.fromGeometry(Point.fromLngLat(
115.79589843749999,
1.5159363834516861)),
Feature.fromGeometry(Point.fromLngLat(
113.291015625,
-0.9667509997666298)),
Feature.fromGeometry(Point.fromLngLat(
116.40083312988281,
-0.3392008994314591))
})
))
.withImage(ICON_ID, BitmapUtils.getBitmapFromDrawable(
getResources().getDrawable(R.drawable.map_marker_push_pin_pink))), new Style.OnStyleLoaded() {
@Override
public void onStyleLoaded(@NonNull Style style) {
ValueAnimatorIconAnimationActivity.this.style = style;
mapView.addOnDidFinishRenderingMapListener(ValueAnimatorIconAnimationActivity.this);
}
}
);
}

/**
* Implementing this interface so that animation only starts once all tiles have been loaded
*
* @param fully whether or not the map is finished rendering
*/
@Override
public void onDidFinishRenderingMap(boolean fully) {
initAnimation(currentSelectedTimeInterpolator);
initInterpolatorButtons();
}

/**
* Initialize and start the animation.
*
* @param desiredTimeInterpolator the type of Android system movement to animate the
* SymbolLayer icons with.
*/
private void initAnimation(TimeInterpolator desiredTimeInterpolator) {
if (animator != null) {
animator.cancel();
}
animator = ValueAnimator.ofFloat(STARTING_DROP_HEIGHT, -17);
animator.setDuration(DROP_SPEED_MILLISECONDS);
animator.setInterpolator(desiredTimeInterpolator);
animator.setStartDelay(1000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (!animationHasStarted) {
initSymbolLayer();
animationHasStarted = true;
}
pinSymbolLayer.setProperties(iconOffset(new Float[] {0f, (Float) valueAnimator.getAnimatedValue()}));
}
});
}

/**
* Add the SymbolLayer to the map
*/
private void initSymbolLayer() {
pinSymbolLayer = new SymbolLayer(SYMBOL_LAYER_ID,
"source-id");
pinSymbolLayer.setProperties(
iconImage(ICON_ID),
iconIgnorePlacement(true),
iconAllowOverlap(true),
iconOffset(new Float[] {0f, DEFAULT_DESIRED_ICON_OFFSET}));
style.addLayer(pinSymbolLayer);
}

/**
* Initialize the interpolator selection spinner menu
*/
private void initInterpolatorButtons() {

FloatingActionButton bounceInterpolatorFab = findViewById(R.id.fab_bounce_interpolator);
bounceInterpolatorFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
currentSelectedTimeInterpolator = new BounceInterpolator();
resetIcons();
}
});

FloatingActionButton linearInterpolatorFab = findViewById(R.id.fab_linear_interpolator);
linearInterpolatorFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
currentSelectedTimeInterpolator = new LinearInterpolator();
firstRunThrough = false;
resetIcons();
}
});

FloatingActionButton accelerateInterpolatorFab = findViewById(R.id.fab_accelerate_interpolator);
accelerateInterpolatorFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
currentSelectedTimeInterpolator = new AccelerateInterpolator();
firstRunThrough = false;
resetIcons();
}
});

FloatingActionButton decelerateInterpolatorFab = findViewById(R.id.fab_decelerate_interpolator);
decelerateInterpolatorFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
currentSelectedTimeInterpolator = new DecelerateInterpolator();
firstRunThrough = false;
resetIcons();
}
});
}

private void resetIcons() {
if (!firstRunThrough) {
animationHasStarted = false;
style.removeLayer(SYMBOL_LAYER_ID);
initAnimation(currentSelectedTimeInterpolator);
}
}

@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}

@Override
public void onResume() {
super.onResume();
mapView.onResume();
}

@Override
public void onPause() {
super.onPause();
mapView.onPause();
}

@Override
protected void onStop() {
super.onStop();
if (animator != null) {
animator.end();
}
mapView.onStop();
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}

@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}

@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
}