Skip to content

Navigation Controls in Flutter

This tutorial shows how to add custom map navigation controls like zoom buttons, compass, and scale indicators to your MapMetrics Flutter map.

Prerequisites

Before you begin, ensure you have:

Basic Zoom Controls

Add simple zoom in/out buttons as a floating overlay:

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

class NavigationControlsScreen extends StatefulWidget {
  @override
  _NavigationControlsScreenState createState() => _NavigationControlsScreenState();
}

class _NavigationControlsScreenState extends State<NavigationControlsScreen> {
  MapMetricsController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Navigation Controls')),
      body: Stack(
        children: [
          // Map
          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,
            ),
          ),
          // Zoom controls (top-right)
          Positioned(
            top: 16,
            right: 16,
            child: Column(
              children: [
                _controlButton(Icons.add, _zoomIn),
                SizedBox(height: 4),
                _controlButton(Icons.remove, _zoomOut),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _controlButton(IconData icon, VoidCallback onPressed) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(4),
        boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
      ),
      child: IconButton(
        icon: Icon(icon, color: Colors.black87),
        onPressed: onPressed,
        constraints: BoxConstraints(minWidth: 40, minHeight: 40),
        padding: EdgeInsets.zero,
      ),
    );
  }

  void _zoomIn() => mapController?.animateCamera(CameraUpdate.zoomIn());
  void _zoomOut() => mapController?.animateCamera(CameraUpdate.zoomOut());
}

Complete Navigation Panel

A full set of controls — zoom, compass, location, and reset:

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

class FullNavigationScreen extends StatefulWidget {
  @override
  _FullNavigationScreenState createState() => _FullNavigationScreenState();
}

class _FullNavigationScreenState extends State<FullNavigationScreen> {
  MapMetricsController? mapController;
  CameraPosition? currentCamera;

  @override
  Widget build(BuildContext context) {
    final bearing = currentCamera?.bearing ?? 0.0;

    return Scaffold(
      body: Stack(
        children: [
          // Map
          MapMetrics(
            styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
            onMapCreated: (controller) => mapController = controller,
            onCameraMove: (position) {
              setState(() {
                currentCamera = position;
              });
            },
            initialCameraPosition: CameraPosition(
              target: LatLng(48.8566, 2.3522),
              zoom: 12.0,
            ),
            myLocationEnabled: true,
          ),
          // Navigation panel (top-right)
          Positioned(
            top: 60,
            right: 16,
            child: Container(
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(8),
                boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 6)],
              ),
              child: Column(
                children: [
                  // Compass (rotates with bearing)
                  IconButton(
                    icon: Transform.rotate(
                      angle: -bearing * 3.14159 / 180,
                      child: Icon(Icons.navigation, color: Colors.red),
                    ),
                    onPressed: _resetNorth,
                    tooltip: 'Reset North',
                  ),
                  Divider(height: 1),
                  // Zoom in
                  IconButton(
                    icon: Icon(Icons.add),
                    onPressed: _zoomIn,
                    tooltip: 'Zoom In',
                  ),
                  Divider(height: 1),
                  // Zoom out
                  IconButton(
                    icon: Icon(Icons.remove),
                    onPressed: _zoomOut,
                    tooltip: 'Zoom Out',
                  ),
                  Divider(height: 1),
                  // My location
                  IconButton(
                    icon: Icon(Icons.my_location, color: Colors.blue),
                    onPressed: _goToMyLocation,
                    tooltip: 'My Location',
                  ),
                  Divider(height: 1),
                  // Reset view
                  IconButton(
                    icon: Icon(Icons.refresh),
                    onPressed: _resetView,
                    tooltip: 'Reset View',
                  ),
                ],
              ),
            ),
          ),
          // Zoom level indicator (bottom-left)
          if (currentCamera != null)
            Positioned(
              bottom: 24,
              left: 16,
              child: Container(
                padding: EdgeInsets.symmetric(horizontal: 10, vertical: 6),
                decoration: BoxDecoration(
                  color: Colors.white.withOpacity(0.9),
                  borderRadius: BorderRadius.circular(4),
                ),
                child: Text(
                  'Zoom: ${currentCamera!.zoom.toStringAsFixed(1)}',
                  style: TextStyle(fontSize: 12, fontFamily: 'monospace'),
                ),
              ),
            ),
        ],
      ),
    );
  }

  void _zoomIn() => mapController?.animateCamera(CameraUpdate.zoomIn());
  void _zoomOut() => mapController?.animateCamera(CameraUpdate.zoomOut());

  void _resetNorth() {
    final target = currentCamera?.target ?? LatLng(48.8566, 2.3522);
    final zoom = currentCamera?.zoom ?? 12.0;
    mapController?.animateCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(target: target, zoom: zoom, bearing: 0, tilt: 0),
      ),
    );
  }

  void _goToMyLocation() {
    mapController?.animateCamera(CameraUpdate.zoomTo(15.0));
  }

  void _resetView() {
    mapController?.animateCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(
          target: LatLng(48.8566, 2.3522),
          zoom: 12.0,
          bearing: 0,
          tilt: 0,
        ),
      ),
    );
  }
}

Control Positioning

PositionUse Case
Top-rightZoom controls, compass (most common)
Top-leftSearch bar, back button
Bottom-rightAttribution, scale bar
Bottom-leftZoom level, coordinates

Next Steps


Tip: Wrap your controls in a Container with a white background and boxShadow to match the look of standard map control panels.