Skip to content

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:

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

CallbackWhen it Fires
onDragStartUser begins dragging the marker
onDragMarker position updates during drag
onDragEndUser releases the marker

Next Steps


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.