Skip to content

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

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


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.