Fly to a Location
This tutorial shows how to animate the camera smoothly to a destination point on your MapMetrics Android map using different animation styles.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Fly-To Animation
Animate the camera to a new location with a button:
kotlin
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
class FlyToActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fly_to)
mapView = findViewById(R.id.mapView)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync { mapMetricsMap ->
map = mapMetricsMap
map.setStyle(
Style.Builder().fromUri(
"https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY"
)
)
// Set initial position
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(51.5074, -0.1278)) // London
.zoom(10.0)
.build()
// Fly to Paris button
findViewById<Button>(R.id.btnFlyToParis).setOnClickListener {
flyToLocation(LatLng(48.8566, 2.3522), 14.0, "Paris")
}
// Fly to Tokyo button
findViewById<Button>(R.id.btnFlyToTokyo).setOnClickListener {
flyToLocation(LatLng(35.6762, 139.6503), 12.0, "Tokyo")
}
}
}
private fun flyToLocation(target: LatLng, zoom: Double, name: String) {
val position = CameraPosition.Builder()
.target(target)
.zoom(zoom)
.tilt(45.0)
.bearing(0.0)
.build()
map.animateCamera(
CameraUpdateFactory.newCameraPosition(position),
3000 // 3 seconds duration
)
}
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() }
override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}Three Animation Styles
Compare moveCamera, easeCamera, and animateCamera:
kotlin
val target = CameraPosition.Builder()
.target(LatLng(48.8566, 2.3522))
.zoom(14.0)
.tilt(45.0)
.build()
// 1. Instant jump — no animation
map.moveCamera(CameraUpdateFactory.newCameraPosition(target))
// 2. Ease — constant speed, smooth but linear
map.easeCamera(
CameraUpdateFactory.newCameraPosition(target),
2000 // duration in ms
)
// 3. Animate — accelerates and decelerates naturally
map.animateCamera(
CameraUpdateFactory.newCameraPosition(target),
3000 // duration in ms
)Fly-To with Callback
Run code when the animation finishes or is cancelled:
kotlin
import org.maplibre.android.maps.MapMetricsMap.CancelableCallback
map.animateCamera(
CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder()
.target(LatLng(40.7128, -74.0060)) // New York
.zoom(15.0)
.tilt(50.0)
.bearing(-20.0)
.build()
),
4000,
object : CancelableCallback {
override fun onFinish() {
// Animation completed — add marker, load data, etc.
map.addMarker(
org.maplibre.android.annotations.MarkerOptions()
.position(LatLng(40.7128, -74.0060))
.title("New York City")
)
}
override fun onCancel() {
// User interrupted the animation by touching the map
}
}
)Tour Through Multiple Locations
Fly through a sequence of locations automatically:
kotlin
import android.os.Handler
import android.os.Looper
private val tourLocations = listOf(
Triple(LatLng(48.8566, 2.3522), 14.0, "Paris"),
Triple(LatLng(41.9028, 12.4964), 13.0, "Rome"),
Triple(LatLng(52.5200, 13.4050), 12.0, "Berlin"),
Triple(LatLng(59.3293, 18.0686), 13.0, "Stockholm"),
Triple(LatLng(40.4168, -3.7038), 12.0, "Madrid"),
)
private var currentIndex = 0
private fun startTour() {
if (currentIndex >= tourLocations.size) {
currentIndex = 0 // loop back
}
val (target, zoom, name) = tourLocations[currentIndex]
val position = CameraPosition.Builder()
.target(target)
.zoom(zoom)
.tilt(50.0)
.bearing(currentIndex * 30.0) // vary the bearing
.build()
map.animateCamera(
CameraUpdateFactory.newCameraPosition(position),
3000,
object : CancelableCallback {
override fun onFinish() {
currentIndex++
// Wait 2 seconds at each stop, then fly to next
Handler(Looper.getMainLooper()).postDelayed({
startTour()
}, 2000)
}
override fun onCancel() {
// User touched the map — stop the tour
}
}
)
}Animation Comparison
| Method | Speed | Feel | Use Case |
|---|---|---|---|
moveCamera | Instant | Abrupt jump | Resetting view, loading saved position |
easeCamera | Constant | Smooth, linear | Short transitions, subtle moves |
animateCamera | Eased | Natural acceleration | Fly-to, showcase, tours |
Next Steps
- Camera Position — Camera position fundamentals
- Animation Types — Deep dive into animation options
- Lat-Lng Bounds — Fit camera to a region
Tip: For long-distance flights (e.g., Paris → Tokyo), use animateCamera with a duration of 4-6 seconds. For short hops within the same city, 1-2 seconds with easeCamera feels more natural.