Handle gestures and clicks
Users can pan, zoom, rotate, and pitch the map by default.
MapInteractions sets which of these camera movements users can
make and what your app does when they click. MapUiOptions sets
which gesture, scroll, or key does what.
Default controls
Section titled “Default controls”Drag with one finger.
Pinch or spread; twist to rotate.
Drag two fingers up or down.
Double-tap, then drag up or down.
For quick zoom, dragging down zooms in. On iOS, dragging up zooms in.
Drag with the left button.
Scroll the wheel.
Drag with the right button.
Slide two fingers.
Pinch or spread two fingers.
Pan and pinch require the host to report trackpad gestures. When it sends scroll events instead, scrolling zooms the map.
Focus the map and press
| Action | Keys |
|---|---|
| Pan | |
| Zoom in | |
| Zoom out | |
| Rotate | |
| Pitch |
Restrict camera movement
Section titled “Restrict camera movement”Turn off a camera movement when your map must stay a certain way, such as north-up and flat. This map can be panned and zoomed, but not rotated or pitched:
MaplibreMap( interactions = MapInteractions { camera { rotate { enabled = false } pitch { enabled = false } } })Settings that you leave out keep their defaults. To turn off every camera
movement, pass MapInteractions.None; clicks still reach the layers’
onClick handlers. To make the map ignore all input, including clicks, pass
MapUiOptions.None as uiOptions. To keep the camera inside an area or a zoom
range instead, see
Limit where the camera can go.
Detect when the user moves the camera
Section titled “Detect when the user moves the camera”Some app behavior should stop when the user moves the map, such as
following the user’s location. Pass a function
to onStart, and the map calls it
each time the user starts to pan:
@Composablefun TrackingMap(onUserPanned: () -> Unit) { MaplibreMap(interactions = MapInteractions { camera { pan { onStart(onUserPanned) } } })}zoom, rotate, and pitch have onStart too.
Respond to map clicks
Section titled “Respond to map clicks”Use callbacks.click to get the geographic position that the user clicked, for
example to drop a pin there:
@Composablefun ClickableMap(onLocationSelected: (Position) -> Unit) { MaplibreMap( interactions = MapInteractions { callbacks { click { onEvent { event -> event.position?.let(onLocationSelected) ClickResult.Consume } } } } )}The callback runs before any layer receives the click. Return
ClickResult.Consume to stop there, or
ClickResult.Pass to let layers receive the click too. For a touch
long press or a right click, use longClick in the same way.
Respond to clicks on a layer
Section titled “Respond to clicks on a layer”Give a layer an onClick handler to let users select its features. This map
reports the earthquake that the user taps, and reports null when they tap
anywhere else:
@Composablefun SelectableEarthquakes(onEarthquakeSelected: (JsonObject?) -> Unit) { val state = rememberMapState { val earthquakes = rememberGeoJsonSource( GeoJsonData.Uri("https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson") ) CircleLayer( id = "earthquakes", source = earthquakes, hitPadding = 12.dp, onClick = { features -> onEarthquakeSelected(features.first().properties) ClickResult.Consume }, ) } MaplibreMap( state = state, interactions = MapInteractions { callbacks { click { onUnhandled { onEarthquakeSelected(null) ClickResult.Pass } } } }, )}onClick receives the layer’s features at the click, topmost first. Its
receiver is the ClickEvent, so the handler can also read
position and screenOffset. hitPadding widens the area around the click
that counts as a hit, which makes small circles and thin lines easier to tap.
When several layers have features at the click, they receive it from the top
layer down until one handler returns ClickResult.Consume. If none does,
onUnhandled runs. To
change how the selected feature looks, see
Query and highlight features.
Change what scrolling does
Section titled “Change what scrolling does”Scrolling zooms the map by default. This map pans when the user scrolls and zooms when they hold Ctrl and scroll:
MaplibreMap( uiOptions = MapUiOptions { bindings { scroll { mappings { on(modifiers = Containing(KeyModifier.Ctrl), action = CameraAction.Zoom) otherwise(CameraAction.Pan) } } } })A mappings block replaces the default mappings. The map uses the first on
row that matches the input, and otherwise matches any other input. Drags, taps, and keys
have their own mappings blocks in bindings.
Snap rotation to bearings
Section titled “Snap rotation to bearings”When a rotation ends within 7° of north, the map turns to exactly north. Use
snapping to choose other bearings. This map snaps to north, east, south, and
west:
MaplibreMap( interactions = MapInteractions { camera { rotate { snapping { targets = BearingTargets.evenlySpaced(count = 4) tolerance = 7.0 } } } })To turn snapping off, set enabled = false in the snapping block.
Change haptic feedback for rotation
Section titled “Change haptic feedback for rotation”On Android, iOS, and macOS, the device gives haptic feedback when the user
rotates the map past north. Use haptics to choose the bearings and how strong
the feedback is. This map gives subtle feedback every 15°, standard feedback at
east, south, and west, and emphasized feedback at north:
MaplibreMap( interactions = MapInteractions { camera { rotate { haptics { notch(BearingTargets.evenlySpaced(24), HapticEmphasis.Subtle) notch(BearingTargets.evenlySpaced(4), HapticEmphasis.Standard) notch(BearingTargets.at(0.0), HapticEmphasis.Emphasized) } } } })A haptics block replaces the default north notch. Where notches overlap, the
strongest emphasis wins.
Pass gestures from your own code
Section titled “Pass gestures from your own code”If your app recognizes gestures itself, for example in a host that is not
Compose UI, pass them to the map with MapState.panBy,
MapState.scaleBy, MapState.fling, and
MapState.click. They follow the same MapInteractions settings
and click callbacks as the built-in gestures. See
Present outside Compose UI for hosts.