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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Fly To
Use MapController.animateCamera to fly to a location:
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:
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:
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:
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
| Method | Description |
|---|---|
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
- Jump to Locations — Navigate through a series of locations
- Set Pitch and Bearing — Control 3D perspective
- Locate the User — Fly to the user's current GPS position
Tip: Use animateCamera for smooth transitions and moveCamera for instant jumps without animation.