Draggable Annotations
Allow users to drag annotations to new positions on the map.
Add draggable symbols and circles over Sydney, then drag one to watch the drag phase and its start and current coordinates update live.
Prerequisites
Annotations only work once the style has loaded and if their type is part of
the widget's annotationOrder. See Prerequisites.
Enable dragging
Dragging needs three things, and all of them are on by default except the last:
MapLibreMap.dragEnabledmust betrue(the default). It is the global switch for the drag gesture listeners.- The annotation type must be listed in
MapLibreMap.annotationConsumeTapEvents(all four types by default). Only the layers of those types are hit-tested, so a type left out of it can never be dragged, even withdraggable: true. - The annotation itself must be created with
draggable: true.
Set draggable: true in SymbolOptions:
final symbol = await controller.addSymbol(
const SymbolOptions(
geometry: LatLng(48.8566, 2.3522),
iconImage: 'my-pin',
draggable: true,
),
);
Listen for drag events
There is a single drag callback, onFeatureDrag, and it covers all three
phases through its DragEventType argument: start, drag (continuous) and
end. For a dragged annotation the annotation argument is the typed object
(Symbol, Circle, Line, Fill), already moved to the new position, so you
only have to react to the drop:
controller.onFeatureDrag.add((
Point<double> point,
LatLng origin,
LatLng current,
LatLng delta,
String id,
Annotation? annotation,
DragEventType eventType,
) {
if (annotation is! Symbol) return;
switch (eventType) {
case DragEventType.start:
print('Drag started at: $origin');
case DragEventType.drag:
print('Dragging: $current');
case DragEventType.end:
// annotation.options.geometry is already the dropped position.
print('Dropped at: ${current.latitude}, ${current.longitude}');
_saveNewPosition(current);
}
});
origin is where the drag started, current the position under the finger,
and delta the movement since the previous event. point is the screen
position, a Point<double> from dart:math.
Toggle draggable at runtime
await controller.updateSymbol(
symbol,
const SymbolOptions(draggable: false), // lock it in place
);
Draggable circles, lines and fills
The draggable property is available on all four annotation types, so
CircleOptions, LineOptions and FillOptions behave exactly like
SymbolOptions, geometry included:
final circle = await controller.addCircle(
CircleOptions(
geometry: const LatLng(48.86, 2.35),
circleRadius: 20,
circleColor: '#296CA8',
draggable: true,
),
);
controller.onFeatureDrag.add((point, origin, current, delta, id, annotation, eventType) {
if (annotation is Circle && eventType == DragEventType.end) {
print('Circle moved to: ${annotation.options.geometry}');
}
});
Dragging style-layer features too
Dragging is not limited to annotations. Features rendered from a GeoJSON source through a style layer can also be dragged. The setup differs:
- Give each feature a
'draggable': trueproperty and a stableid. - Add the layer with
enableInteraction: true. On web, add the source withpromoteId: 'id'so the string id survives;promoteIdis web only, so on Android and iOS the top-levelidfrom step 1 is what identifies the feature. - Listen to
controller.onFeatureDragand write the new position back into the source.
await controller.addGeoJsonSource('points', {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'id': 'p1',
'geometry': {'type': 'Point', 'coordinates': [2.35, 48.86]},
'properties': {'id': 'p1', 'draggable': true},
},
],
}, promoteId: 'id'); // web only, native reads the top-level 'id'
await controller.addCircleLayer(
'points',
'points-layer',
const CircleLayerProperties(circleRadius: 20, circleColor: '#296CA8'),
enableInteraction: true,
);
controller.onFeatureDrag.add((
point, origin, current, delta, id, annotation, eventType,
) {
if (eventType == DragEventType.drag || eventType == DragEventType.end) {
// update the feature's coordinates in your source data, then call
// controller.setGeoJsonSource('points', updatedFeatureCollection);
}
});
The difference is not the callback, both go through onFeatureDrag: for an annotation the manager moves the geometry for you and hands you the typed object, while for a style-layer feature annotation is null and you own the source update. See Annotations vs Style Layers.