Skip to content

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:

Jump vs Fly

MethodDescription
moveCameraInstant jump — no animation
animateCameraSmooth 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

MethodAnimationUse Case
moveCamera(CameraUpdate)None (instant)Quick navigation, user-triggered jumps
animateCamera(CameraUpdate)Smooth flyVisual transitions, tours, presentations
animateCamera(CameraUpdate, duration)Custom speedCinematic effects, slow reveals

Next Steps


Tip: Combine animateCamera with different bearing and tilt values at each stop for a cinematic world tour effect.