Jump to Locations in Flutter
This tutorial shows how to navigate the map through a series of locations — either instantly with moveCamera or with smooth animation using animateCamera.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Jump vs Fly
| Method | Description |
|---|---|
moveCamera | Instant jump — no animation |
animateCamera | Smooth fly — animated transition |
Basic Jump to Locations
Provide buttons that instantly jump to different cities:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class JumpToLocationsScreen extends StatefulWidget {
@override
_JumpToLocationsScreenState createState() => _JumpToLocationsScreenState();
}
class _JumpToLocationsScreenState extends State<JumpToLocationsScreen> {
MapMetricsController? mapController;
String currentCity = 'Paris';
final List<Map<String, dynamic>> locations = [
{'name': 'Paris', 'position': LatLng(48.8566, 2.3522), 'zoom': 12.0},
{'name': 'New York', 'position': LatLng(40.7128, -74.0060), 'zoom': 12.0},
{'name': 'Tokyo', 'position': LatLng(35.6895, 139.6917), 'zoom': 12.0},
{'name': 'Sydney', 'position': LatLng(-33.8688, 151.2093), 'zoom': 12.0},
{'name': 'Dubai', 'position': LatLng(25.2048, 55.2708), 'zoom': 12.0},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Jump to Locations')),
body: Column(
children: [
// Location buttons
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.all(8),
child: Row(
children: locations.map((loc) {
final isActive = currentCity == loc['name'];
return Padding(
padding: EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(loc['name']),
selected: isActive,
onSelected: (_) => _jumpTo(loc),
selectedColor: Colors.blue[100],
),
);
}).toList(),
),
),
// 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: 12.0,
),
),
),
],
),
);
}
void _jumpTo(Map<String, dynamic> location) {
setState(() {
currentCity = location['name'];
});
// Instant jump — no animation
mapController?.moveCamera(
CameraUpdate.newLatLngZoom(location['position'], location['zoom']),
);
}
}Animated Navigation
Use animateCamera for a smooth transition between locations:
dart
void _flyTo(Map<String, dynamic> location) {
setState(() {
currentCity = location['name'];
});
// Smooth animated flight
mapController?.animateCamera(
CameraUpdate.newLatLngZoom(location['position'], location['zoom']),
);
}Auto Tour: Cycle Through Locations
Automatically cycle through a list of locations with a timer:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
import 'dart:async';
class AutoTourScreen extends StatefulWidget {
@override
_AutoTourScreenState createState() => _AutoTourScreenState();
}
class _AutoTourScreenState extends State<AutoTourScreen> {
MapMetricsController? mapController;
Timer? tourTimer;
int currentIndex = 0;
bool isTourRunning = false;
final List<Map<String, dynamic>> locations = [
{
'name': 'Paris',
'position': LatLng(48.8566, 2.3522),
'zoom': 14.0,
'bearing': 0.0,
'tilt': 45.0,
},
{
'name': 'New York',
'position': LatLng(40.7128, -74.0060),
'zoom': 14.0,
'bearing': 90.0,
'tilt': 50.0,
},
{
'name': 'Tokyo',
'position': LatLng(35.6895, 139.6917),
'zoom': 14.0,
'bearing': 180.0,
'tilt': 45.0,
},
{
'name': 'Sydney',
'position': LatLng(-33.8688, 151.2093),
'zoom': 14.0,
'bearing': 270.0,
'tilt': 50.0,
},
{
'name': 'Dubai',
'position': LatLng(25.2048, 55.2708),
'zoom': 14.0,
'bearing': 45.0,
'tilt': 55.0,
},
];
@override
Widget build(BuildContext context) {
final current = locations[currentIndex];
return Scaffold(
appBar: AppBar(
title: Text('World Tour'),
),
body: Stack(
children: [
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: 3.0,
),
),
// City name overlay
Positioned(
top: 16,
left: 16,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(8),
),
child: Text(
current['name'],
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
// Controls
Positioned(
bottom: 24,
left: 16,
right: 16,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Previous
FloatingActionButton.small(
heroTag: 'prev',
onPressed: _previous,
child: Icon(Icons.skip_previous),
),
SizedBox(width: 12),
// Play/Pause
FloatingActionButton(
heroTag: 'play',
onPressed: _toggleTour,
child: Icon(isTourRunning ? Icons.pause : Icons.play_arrow),
),
SizedBox(width: 12),
// Next
FloatingActionButton.small(
heroTag: 'next',
onPressed: _next,
child: Icon(Icons.skip_next),
),
],
),
),
],
),
);
}
void _navigateTo(int index) {
final location = locations[index];
mapController?.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: location['position'],
zoom: location['zoom'],
bearing: location['bearing'],
tilt: location['tilt'],
),
),
);
}
void _next() {
setState(() {
currentIndex = (currentIndex + 1) % locations.length;
});
_navigateTo(currentIndex);
}
void _previous() {
setState(() {
currentIndex = (currentIndex - 1 + locations.length) % locations.length;
});
_navigateTo(currentIndex);
}
void _toggleTour() {
if (isTourRunning) {
tourTimer?.cancel();
setState(() {
isTourRunning = false;
});
} else {
setState(() {
isTourRunning = true;
});
_navigateTo(currentIndex);
tourTimer = Timer.periodic(Duration(seconds: 5), (_) {
_next();
});
}
}
@override
void dispose() {
tourTimer?.cancel();
mapController?.dispose();
super.dispose();
}
}Camera Methods Comparison
| Method | Animation | Use Case |
|---|---|---|
moveCamera(CameraUpdate) | None (instant) | Quick navigation, user-triggered jumps |
animateCamera(CameraUpdate) | Smooth fly | Visual transitions, tours, presentations |
animateCamera(CameraUpdate, duration) | Custom speed | Cinematic effects, slow reveals |
Next Steps
- Fly to a Location — Deep dive into fly-to animations
- Set Pitch and Bearing — 3D perspectives for each stop
- Map Interactions — Handle taps and gestures
Tip: Combine animateCamera with different bearing and tilt values at each stop for a cinematic world tour effect.