Draggable Marker in Flutter
This tutorial shows how to create markers that users can drag to a new position on the map. This is useful for letting users pick a location, adjust a pin, or reposition points of interest.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Draggable Marker
Set draggable: true and use onDragEnd to get the new position:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class DraggableMarkerScreen extends StatefulWidget {
@override
_DraggableMarkerScreenState createState() => _DraggableMarkerScreenState();
}
class _DraggableMarkerScreenState extends State<DraggableMarkerScreen> {
MapMetricsController? mapController;
LatLng markerPosition = LatLng(48.8566, 2.3522);
Set<Marker> get markers => {
Marker(
markerId: MarkerId('draggable'),
position: markerPosition,
draggable: true,
onDragEnd: (LatLng newPosition) {
setState(() {
markerPosition = newPosition;
});
},
infoWindow: InfoWindow(
title: 'Drag me!',
snippet: 'Long press and drag to move',
),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
),
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Draggable Marker')),
body: Column(
children: [
// Coordinate display
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
color: Colors.grey[100],
child: Text(
'Position: ${markerPosition.latitude.toStringAsFixed(6)}, '
'${markerPosition.longitude.toStringAsFixed(6)}',
style: TextStyle(fontFamily: 'monospace', fontSize: 14),
),
),
// Map
Expanded(
child: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 14.0,
),
markers: markers,
),
),
],
),
);
}
}Long-press the marker and drag it to a new location. The coordinate display updates in real time.
Drag Events
Track all stages of the drag — start, move, and end:
dart
Marker(
markerId: MarkerId('tracked_drag'),
position: markerPosition,
draggable: true,
onDragStart: (LatLng startPosition) {
print('Drag started at: $startPosition');
// You could show a visual indicator like a shadow
},
onDrag: (LatLng currentPosition) {
// Called continuously while dragging
// Useful for updating a real-time coordinate display
setState(() {
markerPosition = currentPosition;
});
},
onDragEnd: (LatLng endPosition) {
print('Drag ended at: $endPosition');
setState(() {
markerPosition = endPosition;
});
},
)Complete Example: Location Picker
A practical example where the user drags a marker to select a delivery address:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class LocationPickerScreen extends StatefulWidget {
@override
_LocationPickerScreenState createState() => _LocationPickerScreenState();
}
class _LocationPickerScreenState extends State<LocationPickerScreen> {
MapMetricsController? mapController;
LatLng selectedPosition = LatLng(48.8566, 2.3522);
bool isDragging = false;
Set<Marker> get markers => {
Marker(
markerId: MarkerId('picker'),
position: selectedPosition,
draggable: true,
onDragStart: (LatLng position) {
setState(() {
isDragging = true;
});
},
onDragEnd: (LatLng newPosition) {
setState(() {
selectedPosition = newPosition;
isDragging = false;
});
},
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
),
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Pick a Location')),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: selectedPosition,
zoom: 15.0,
),
markers: markers,
),
// Bottom card with confirm button
Positioned(
bottom: 24,
left: 16,
right: 16,
child: Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
isDragging ? 'Release to select...' : 'Drag the pin to your location',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
SizedBox(height: 8),
Text(
'Lat: ${selectedPosition.latitude.toStringAsFixed(6)}\n'
'Lng: ${selectedPosition.longitude.toStringAsFixed(6)}',
style: TextStyle(
fontFamily: 'monospace',
fontSize: 13,
color: Colors.grey[600],
),
),
SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isDragging
? null
: () {
// Use selectedPosition for your app logic
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Location confirmed: '
'${selectedPosition.latitude.toStringAsFixed(4)}, '
'${selectedPosition.longitude.toStringAsFixed(4)}',
),
),
);
},
child: Text('Confirm Location'),
),
),
],
),
),
),
),
],
),
);
}
}Marker Drag Properties
| Property | Type | Description |
|---|---|---|
draggable | bool | Enable/disable dragging (default: false) |
onDragStart | ValueChanged<LatLng> | Called when the user starts dragging |
onDrag | ValueChanged<LatLng> | Called continuously while dragging |
onDragEnd | ValueChanged<LatLng> | Called when the user releases the marker |
Next Steps
- Add a Popup — Show info when tapping markers
- Markers and Annotations — Learn more about marker customization
- Map Interactions — Handle all types of user interaction
Tip: Long-press on the marker to start dragging it. A regular tap will open the info window if one is set.