Toggle Map Interactions
This tutorial shows how to individually enable and disable specific map gestures at runtime — giving users fine-grained control over how they interact with the map.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Interactive Toggle Panel
Create a panel of switches to control each gesture:
kotlin
import android.os.Bundle
import android.widget.Switch
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.camera.CameraPosition
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 ToggleInteractionsActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_toggle_interactions)
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"
)
)
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(48.8566, 2.3522))
.zoom(13.0)
.tilt(30.0)
.build()
setupToggles()
}
}
private fun setupToggles() {
val ui = map.uiSettings
// Scroll (pan)
bindToggle(R.id.switchScroll, "Scroll/Pan", ui.isScrollGesturesEnabled) {
ui.isScrollGesturesEnabled = it
}
// Zoom (pinch)
bindToggle(R.id.switchZoom, "Pinch Zoom", ui.isZoomGesturesEnabled) {
ui.isZoomGesturesEnabled = it
}
// Double-tap zoom
bindToggle(R.id.switchDoubleTap, "Double-Tap Zoom", ui.isDoubleTapGesturesEnabled) {
ui.isDoubleTapGesturesEnabled = it
}
// Rotate
bindToggle(R.id.switchRotate, "Rotate", ui.isRotateGesturesEnabled) {
ui.isRotateGesturesEnabled = it
}
// Tilt
bindToggle(R.id.switchTilt, "Tilt", ui.isTiltGesturesEnabled) {
ui.isTiltGesturesEnabled = it
}
// Fling momentum
bindToggle(R.id.switchFling, "Fling Momentum", ui.isFlingVelocityAnimationEnabled) {
ui.isFlingVelocityAnimationEnabled = it
}
}
private fun bindToggle(
viewId: Int,
label: String,
initialValue: Boolean,
onChange: (Boolean) -> Unit
) {
findViewById<Switch>(viewId).apply {
text = label
isChecked = initialValue
setOnCheckedChangeListener { _, checked -> onChange(checked) }
}
}
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)
}
}Layout (res/layout/activity_toggle_interactions.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.maplibre.android.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:background="#F5F5F5">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toggle Interactions"
android:textStyle="bold"
android:textSize="16sp"
android:layout_marginBottom="8dp" />
<Switch android:id="@+id/switchScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch android:id="@+id/switchZoom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch android:id="@+id/switchDoubleTap"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch android:id="@+id/switchRotate"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch android:id="@+id/switchTilt"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch android:id="@+id/switchFling"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>Preset Interaction Modes
Quickly switch between common configurations:
kotlin
import android.widget.Button
private fun setupPresets() {
val ui = map.uiSettings
// Full interaction
findViewById<Button>(R.id.btnFullMode).setOnClickListener {
ui.setAllGesturesEnabled(true)
ui.isFlingVelocityAnimationEnabled = true
refreshToggles()
}
// View-only (no interaction)
findViewById<Button>(R.id.btnViewOnly).setOnClickListener {
ui.setAllGesturesEnabled(false)
ui.isFlingVelocityAnimationEnabled = false
refreshToggles()
}
// Pan-only (embedded map)
findViewById<Button>(R.id.btnPanOnly).setOnClickListener {
ui.isScrollGesturesEnabled = true
ui.isZoomGesturesEnabled = false
ui.isDoubleTapGesturesEnabled = false
ui.isRotateGesturesEnabled = false
ui.isTiltGesturesEnabled = false
refreshToggles()
}
// 2D mode (no rotate/tilt)
findViewById<Button>(R.id.btn2dMode).setOnClickListener {
ui.isScrollGesturesEnabled = true
ui.isZoomGesturesEnabled = true
ui.isDoubleTapGesturesEnabled = true
ui.isRotateGesturesEnabled = false
ui.isTiltGesturesEnabled = false
// Reset to flat view
map.animateCamera(
org.maplibre.android.camera.CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder(map.cameraPosition)
.bearing(0.0)
.tilt(0.0)
.build()
),
500
)
refreshToggles()
}
}
private fun refreshToggles() {
val ui = map.uiSettings
findViewById<Switch>(R.id.switchScroll).isChecked = ui.isScrollGesturesEnabled
findViewById<Switch>(R.id.switchZoom).isChecked = ui.isZoomGesturesEnabled
findViewById<Switch>(R.id.switchDoubleTap).isChecked = ui.isDoubleTapGesturesEnabled
findViewById<Switch>(R.id.switchRotate).isChecked = ui.isRotateGesturesEnabled
findViewById<Switch>(R.id.switchTilt).isChecked = ui.isTiltGesturesEnabled
findViewById<Switch>(R.id.switchFling).isChecked = ui.isFlingVelocityAnimationEnabled
}Next Steps
- Disable Gestures — Static map and gesture settings
- Gesture Detector — Advanced gesture handling
- Game-Style Controls — D-pad navigation
Tip: For maps embedded inside scrollable content (like a RecyclerView), disable scroll and zoom gestures so the map doesn't steal touch events from the parent scroll view.