Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

Fly to a Location in Flutter

Smoothly animate the map camera to fly to any location. This is great for navigation UIs where you want the map to glide to a destination.

Prerequisites

Before you begin, ensure you have:

Basic Fly To

Use MapController.animateCamera to fly to a location:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class FlyToLocationScreen extends StatefulWidget {
  @override
  _FlyToLocationScreenState createState() => _FlyToLocationScreenState();
}

class _FlyToLocationScreenState extends State<FlyToLocationScreen> {
  MapController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fly to a Location')),
      body: Column(
        children: [
          // Buttons row
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            padding: EdgeInsets.all(8),
            child: Row(
              children: [
                _buildLocationButton('New York', Position(-74.0060, 40.7128)),
                SizedBox(width: 8),
                _buildLocationButton('London', Position(-0.1276, 51.5074)),
                SizedBox(width: 8),
                _buildLocationButton('Tokyo', Position(139.6917, 35.6895)),
                SizedBox(width: 8),
                _buildLocationButton('Paris', Position(2.3522, 48.8566)),
              ],
            ),
          ),
          // Map
          Expanded(
            child: MapMetricsView(
              options: MapOptions(
                initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
                initZoom: 3,
                initStyle:
                    'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
              ),
              onMapCreated: (MapController controller) {
                mapController = controller;
              },
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildLocationButton(String label, Position target) {
    return ElevatedButton(
      onPressed: () => _flyTo(target),
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
        ),
      ),
      child: Text(label),
    );
  }

  void _flyTo(Position target) {
    mapController?.animateCamera(center: target, zoom: 12);
  }
}

Tap any button and the map will smoothly fly to that city.

Fly To with Bearing and Tilt

For a more dramatic fly-to effect, change the bearing (rotation) and pitch (tilt) at the same time:

dart
void _flyToWithPerspective(Position target) {
  mapController?.animateCamera(
    center: target,
    zoom: 15,
    bearing: 45, // Rotate 45 degrees
    pitch: 50,   // Tilt for a 3D perspective
  );
}

Fly To with Custom Duration

Control the animation speed with the nativeDuration parameter:

dart
void _slowFlyTo(Position target) {
  mapController?.animateCamera(
    center: target,
    zoom: 14,
    nativeDuration: Duration(seconds: 3), // Slow, cinematic flight
  );
}

void _fastFlyTo(Position target) {
  mapController?.animateCamera(
    center: target,
    zoom: 14,
    nativeDuration: Duration(milliseconds: 500), // Quick snap
  );
}

Complete Example: City Tour

Build a city tour that automatically cycles through locations:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
import 'dart:async';

class CityTourScreen extends StatefulWidget {
  @override
  _CityTourScreenState createState() => _CityTourScreenState();
}

class _CityTourScreenState extends State<CityTourScreen> {
  MapController? mapController;
  Timer? tourTimer;
  int currentIndex = 0;
  bool isTourRunning = false;

  final List<Map<String, dynamic>> cities = [
    {'name': 'Paris', 'position': Position(2.3522, 48.8566)},
    {'name': 'New York', 'position': Position(-74.0060, 40.7128)},
    {'name': 'Tokyo', 'position': Position(139.6917, 35.6895)},
    {'name': 'Sydney', 'position': Position(151.2093, -33.8688)},
    {'name': 'Dubai', 'position': Position(55.2708, 25.2048)},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('City Tour'),
        actions: [
          TextButton.icon(
            onPressed: _toggleTour,
            icon: Icon(
              isTourRunning ? Icons.stop : Icons.play_arrow,
              color: Colors.white,
            ),
            label: Text(
              isTourRunning ? 'Stop Tour' : 'Start Tour',
              style: TextStyle(color: Colors.white),
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          // City buttons
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            padding: EdgeInsets.all(8),
            child: Row(
              children: cities.map((city) {
                final isActive = cities[currentIndex]['name'] == city['name'];
                return Padding(
                  padding: EdgeInsets.only(right: 8),
                  child: ElevatedButton(
                    onPressed: () {
                      setState(() {
                        currentIndex = cities.indexOf(city);
                      });
                      _flyTo(city['position'] as Position);
                    },
                    style: ElevatedButton.styleFrom(
                      backgroundColor: isActive ? Colors.blue : Colors.grey[300],
                      foregroundColor: isActive ? Colors.white : Colors.black87,
                    ),
                    child: Text(city['name']),
                  ),
                );
              }).toList(),
            ),
          ),
          // Map
          Expanded(
            child: MapMetricsView(
              options: MapOptions(
                initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
                initZoom: 3,
                initStyle:
                    'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
              ),
              onMapCreated: (MapController controller) {
                mapController = controller;
              },
            ),
          ),
        ],
      ),
    );
  }

  void _flyTo(Position target) {
    mapController?.animateCamera(center: target, zoom: 12);
  }

  void _toggleTour() {
    if (isTourRunning) {
      tourTimer?.cancel();
      setState(() {
        isTourRunning = false;
      });
    } else {
      setState(() {
        isTourRunning = true;
      });
      _flyTo(cities[currentIndex]['position'] as Position);
      tourTimer = Timer.periodic(Duration(seconds: 4), (timer) {
        setState(() {
          currentIndex = (currentIndex + 1) % cities.length;
        });
        _flyTo(cities[currentIndex]['position'] as Position);
      });
    }
  }

  @override
  void dispose() {
    tourTimer?.cancel();
    super.dispose();
  }
}

Camera Methods

MethodDescription
MapController.moveCamera({center, zoom, bearing, pitch})Instant jump, no animation
MapController.animateCamera({center, zoom, bearing, pitch, nativeDuration})Smooth animated transition (defaults to 2s)
MapController.fitBounds({bounds, padding, ...})Animate the camera to fit a bounding box
MapController.getCamera()Read the current MapCamera (center, zoom, bearing, pitch) synchronously

Next Steps


Tip: Use animateCamera for smooth transitions and moveCamera for instant jumps without animation.