Create a Draggable Point in Flutter
This tutorial shows how to create a draggable point that draws a line or shape as you drag it — useful for route planning, area selection, or interactive drawing tools.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Draggable Point with Trail
Drag a point on the map and leave a trail line behind:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class DraggablePointTrailScreen extends StatefulWidget {
@override
_DraggablePointTrailScreenState createState() =>
_DraggablePointTrailScreenState();
}
class _DraggablePointTrailScreenState
extends State<DraggablePointTrailScreen> {
MapMetricsController? mapController;
LatLng currentPosition = LatLng(48.8566, 2.3522);
List<LatLng> trail = [LatLng(48.8566, 2.3522)];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable Point with Trail'),
actions: [
IconButton(
icon: Icon(Icons.clear),
tooltip: 'Clear trail',
onPressed: () {
setState(() {
trail = [currentPosition];
});
},
),
],
),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 13.0,
),
markers: {
Marker(
markerId: MarkerId('draggable'),
position: currentPosition,
draggable: true,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueBlue),
infoWindow: InfoWindow(title: 'Drag me!'),
onDragEnd: (LatLng newPosition) {
setState(() {
currentPosition = newPosition;
trail.add(newPosition);
});
},
),
},
polylines: trail.length > 1
? {
Polyline(
polylineId: PolylineId('trail'),
points: trail,
color: Colors.blue,
width: 3,
patterns: [PatternItem.dash(10), PatternItem.gap(5)],
),
}
: {},
),
);
}
}Two-Point Distance Measurer
Drag two points to measure the distance between them:
dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class TwoPointDragScreen extends StatefulWidget {
@override
_TwoPointDragScreenState createState() => _TwoPointDragScreenState();
}
class _TwoPointDragScreenState extends State<TwoPointDragScreen> {
MapMetricsController? mapController;
LatLng pointA = LatLng(48.8584, 2.2945); // Eiffel Tower
LatLng pointB = LatLng(48.8606, 2.3376); // Louvre
/// Haversine distance in km
double _distanceKm(LatLng a, LatLng b) {
const R = 6371.0;
final dLat = _toRad(b.latitude - a.latitude);
final dLon = _toRad(b.longitude - a.longitude);
final sinLat = sin(dLat / 2);
final sinLon = sin(dLon / 2);
final h = sinLat * sinLat +
cos(_toRad(a.latitude)) * cos(_toRad(b.latitude)) * sinLon * sinLon;
return R * 2 * atan2(sqrt(h), sqrt(1 - h));
}
double _toRad(double deg) => deg * pi / 180;
@override
Widget build(BuildContext context) {
final distance = _distanceKm(pointA, pointB);
return Scaffold(
appBar: AppBar(title: Text('Distance Measurer')),
body: Stack(
children: [
MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8590, 2.3160),
zoom: 14.0,
),
markers: {
Marker(
markerId: MarkerId('pointA'),
position: pointA,
draggable: true,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueGreen),
infoWindow: InfoWindow(title: 'Point A'),
onDragEnd: (pos) => setState(() => pointA = pos),
),
Marker(
markerId: MarkerId('pointB'),
position: pointB,
draggable: true,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueRed),
infoWindow: InfoWindow(title: 'Point B'),
onDragEnd: (pos) => setState(() => pointB = pos),
),
},
polylines: {
Polyline(
polylineId: PolylineId('measurement'),
points: [pointA, pointB],
color: Colors.orange,
width: 3,
),
},
),
// Distance display
Positioned(
top: 16,
left: 16,
right: 16,
child: Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${distance.toStringAsFixed(2)} km',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
Text(
'(${(distance * 1000).toStringAsFixed(0)} meters)',
style: TextStyle(color: Colors.grey[600]),
),
SizedBox(height: 4),
Text(
'Drag the markers to measure',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
),
),
],
),
);
}
}Polygon Drawing with Draggable Vertices
Create a polygon by tapping, then adjust vertices by dragging:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class DraggablePolygonScreen extends StatefulWidget {
@override
_DraggablePolygonScreenState createState() =>
_DraggablePolygonScreenState();
}
class _DraggablePolygonScreenState extends State<DraggablePolygonScreen> {
MapMetricsController? mapController;
List<LatLng> vertices = [
LatLng(48.860, 2.330),
LatLng(48.860, 2.360),
LatLng(48.845, 2.360),
LatLng(48.845, 2.330),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Draggable Polygon')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.852, 2.345),
zoom: 14.0,
),
// Draggable vertex markers
markers: vertices.asMap().entries.map((entry) {
final i = entry.key;
return Marker(
markerId: MarkerId('vertex_$i'),
position: entry.value,
draggable: true,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueBlue),
onDragEnd: (newPos) {
setState(() {
vertices[i] = newPos;
});
},
);
}).toSet(),
// Polygon shape
polygons: {
Polygon(
polygonId: PolygonId('editable'),
points: vertices,
fillColor: Colors.blue.withOpacity(0.2),
strokeColor: Colors.blue,
strokeWidth: 2,
),
},
),
);
}
}Drag Event Callbacks
| Callback | When it Fires |
|---|---|
onDragStart | User begins dragging the marker |
onDrag | Marker position updates during drag |
onDragEnd | User releases the marker |
Next Steps
- Draggable Marker — Basic draggable marker
- Measure Distances — Full measurement tool
- Get Coordinates on Tap — Tap for coordinates
Tip: Use onDrag for live updates during dragging (like showing a live distance measurement) and onDragEnd for final actions (like saving the position). Be mindful that onDrag fires very frequently — debounce expensive operations.