Animator Animation
This example showcases how to use the Animator API to schedule a sequence of map animations.
CameraAnimatorActivity.kt
package org.maplibre.android.testapp.activity.camera
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.TypeEvaluator
import android.animation.ValueAnimator
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.animation.AnticipateOvershootInterpolator
import android.view.animation.BounceInterpolator
import android.view.animation.Interpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.LongSparseArray
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.*
import org.maplibre.android.testapp.R
import org.maplibre.android.testapp.styles.TestStyles
/** Test activity showcasing using Android SDK animators to animate camera position changes. */
class CameraAnimatorActivity : AppCompatActivity(), OnMapReadyCallback {
private val animators = LongSparseArray<Animator>()
private lateinit var set: Animator
private lateinit var mapView: MapView
private lateinit var maplibreMap: MapLibreMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera_animator)
mapView = findViewById<View>(R.id.mapView) as MapView
if (::mapView.isInitialized) {
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
}
}
override fun onMapReady(map: MapLibreMap) {
maplibreMap = map
map.setStyle(TestStyles.getPredefinedStyleWithFallback("Streets"))
initFab()
}
private fun initFab() {
findViewById<View>(R.id.fab).setOnClickListener { view: View ->
view.visibility = View.GONE
val animatedPosition =
CameraPosition.Builder()
.target(LatLng(37.789992, -122.402214))
.tilt(60.0)
.zoom(14.5)
.bearing(135.0)
.build()
set = createExampleAnimator(maplibreMap.cameraPosition, animatedPosition)
set.start()
}
}
//
// Animator API used for the animation on the FAB
//
private fun createExampleAnimator(
currentPosition: CameraPosition,
targetPosition: CameraPosition
): Animator {
val animatorSet = AnimatorSet()
animatorSet.play(createLatLngAnimator(currentPosition.target!!, targetPosition.target!!))
animatorSet.play(createZoomAnimator(currentPosition.zoom, targetPosition.zoom))
animatorSet.play(createBearingAnimator(currentPosition.bearing, targetPosition.bearing))
animatorSet.play(createTiltAnimator(currentPosition.tilt, targetPosition.tilt))
return animatorSet
}
private fun createLatLngAnimator(currentPosition: LatLng, targetPosition: LatLng): Animator {
val latLngAnimator =
ValueAnimator.ofObject(LatLngEvaluator(), currentPosition, targetPosition)
latLngAnimator.duration = (1000 * ANIMATION_DELAY_FACTOR).toLong()
latLngAnimator.interpolator = FastOutSlowInInterpolator()
latLngAnimator.addUpdateListener { animation: ValueAnimator ->
maplibreMap.moveCamera(
CameraUpdateFactory.newLatLng((animation.animatedValue as LatLng))
)
}
return latLngAnimator
}
private fun createZoomAnimator(currentZoom: Double, targetZoom: Double): Animator {
val zoomAnimator = ValueAnimator.ofFloat(currentZoom.toFloat(), targetZoom.toFloat())
zoomAnimator.duration = (2200 * ANIMATION_DELAY_FACTOR).toLong()
zoomAnimator.startDelay = (600 * ANIMATION_DELAY_FACTOR).toLong()
zoomAnimator.interpolator = AnticipateOvershootInterpolator()
zoomAnimator.addUpdateListener { animation: ValueAnimator ->
maplibreMap.moveCamera(
CameraUpdateFactory.zoomTo((animation.animatedValue as Float).toDouble())
)
}
return zoomAnimator
}
private fun createBearingAnimator(currentBearing: Double, targetBearing: Double): Animator {
val bearingAnimator =
ValueAnimator.ofFloat(currentBearing.toFloat(), targetBearing.toFloat())
bearingAnimator.duration = (1000 * ANIMATION_DELAY_FACTOR).toLong()
bearingAnimator.startDelay = (1000 * ANIMATION_DELAY_FACTOR).toLong()
bearingAnimator.interpolator = FastOutLinearInInterpolator()
bearingAnimator.addUpdateListener { animation: ValueAnimator ->
maplibreMap.moveCamera(
CameraUpdateFactory.bearingTo((animation.animatedValue as Float).toDouble())
)
}
return bearingAnimator
}
private fun createTiltAnimator(currentTilt: Double, targetTilt: Double): Animator {
val tiltAnimator = ValueAnimator.ofFloat(currentTilt.toFloat(), targetTilt.toFloat())
tiltAnimator.duration = (1000 * ANIMATION_DELAY_FACTOR).toLong()
tiltAnimator.startDelay = (1500 * ANIMATION_DELAY_FACTOR).toLong()
tiltAnimator.addUpdateListener { animation: ValueAnimator ->
maplibreMap.moveCamera(
CameraUpdateFactory.tiltTo((animation.animatedValue as Float).toDouble())
)
}
return tiltAnimator
}
//
// Interpolator examples
//
private fun obtainExampleInterpolator(menuItemId: Int): Animator? {
return animators[menuItemId.toLong()]
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_animator, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (!::maplibreMap.isInitialized) {
return false
}
if (item.itemId != android.R.id.home) {
findViewById<View>(R.id.fab).visibility = View.GONE
resetCameraPosition()
playAnimation(item.itemId)
}
return super.onOptionsItemSelected(item)
}
private fun resetCameraPosition() {
maplibreMap.moveCamera(
CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder()
.target(START_LAT_LNG)
.zoom(11.0)
.bearing(0.0)
.tilt(0.0)
.build()
)
)
}
private fun playAnimation(itemId: Int) {
val animator = obtainExampleInterpolator(itemId)
if (animator != null) {
animator.cancel()
animator.start()
}
}
private fun obtainExampleInterpolator(interpolator: Interpolator, duration: Long): Animator {
val zoomAnimator = ValueAnimator.ofFloat(11.0f, 16.0f)
zoomAnimator.duration = (duration * ANIMATION_DELAY_FACTOR).toLong()
zoomAnimator.interpolator = interpolator
zoomAnimator.addUpdateListener { animation: ValueAnimator ->
maplibreMap.moveCamera(
CameraUpdateFactory.zoomTo((animation.animatedValue as Float).toDouble())
)
}
return zoomAnimator
}
//
// MapView lifecycle
//
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onStop() {
super.onStop()
mapView.onStop()
for (i in 0 until animators.size()) {
animators[animators.keyAt(i)]!!.cancel()
}
if (this::set.isInitialized) {
set.cancel()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
override fun onDestroy() {
super.onDestroy()
if (::mapView.isInitialized) {
mapView.onDestroy()
}
}
override fun onLowMemory() {
super.onLowMemory()
if (::mapView.isInitialized) {
mapView.onLowMemory()
}
}
/** Helper class to evaluate LatLng objects with a ValueAnimator */
private class LatLngEvaluator : TypeEvaluator<LatLng> {
private val latLng = LatLng()
override fun evaluate(fraction: Float, startValue: LatLng, endValue: LatLng): LatLng {
latLng.latitude = startValue.latitude + (endValue.latitude - startValue.latitude) * fraction
latLng.longitude = startValue.longitude + (endValue.longitude - startValue.longitude) * fraction
return latLng
}
}
companion object {
private const val ANIMATION_DELAY_FACTOR = 1.5
private val START_LAT_LNG = LatLng(37.787947, -122.407432)
}
init {
val accelerateDecelerateAnimatorSet = AnimatorSet()
accelerateDecelerateAnimatorSet.playTogether(
createLatLngAnimator(START_LAT_LNG, LatLng(37.826715, -122.422795)),
obtainExampleInterpolator(FastOutSlowInInterpolator(), 2500)
)
animators.put(
R.id.menu_action_accelerate_decelerate_interpolator.toLong(),
accelerateDecelerateAnimatorSet
)
val bounceAnimatorSet = AnimatorSet()
bounceAnimatorSet.playTogether(
createLatLngAnimator(START_LAT_LNG, LatLng(37.787947, -122.407432)),
obtainExampleInterpolator(BounceInterpolator(), 3750)
)
animators.put(R.id.menu_action_bounce_interpolator.toLong(), bounceAnimatorSet)
animators.put(
R.id.menu_action_anticipate_overshoot_interpolator.toLong(),
obtainExampleInterpolator(AnticipateOvershootInterpolator(), 2500)
)
animators.put(
R.id.menu_action_path_interpolator.toLong(),
obtainExampleInterpolator(
PathInterpolatorCompat.create(.22f, .68f, 0f, 1.71f),
2500
)
)
}
}