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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Zoom Controls
Add simple zoom in/out buttons as a floating overlay:
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class NavigationControlsScreen extends StatefulWidget {
@override
_NavigationControlsScreenState createState() => _NavigationControlsScreenState();
}
class _NavigationControlsScreenState extends State<NavigationControlsScreen> {
MapController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Navigation Controls')),
body: Stack(
children: [
// Map
MapMetricsView(
options: MapOptions(
initStyle: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
initCenter: Position(2.3522, 48.8566), // Paris (lng, lat)
initZoom: 12.0,
),
onMapCreated: (controller) => mapController = controller,
),
// 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() {
final zoom = mapController?.getCamera().zoom ?? 12.0;
mapController?.animateCamera(zoom: zoom + 1);
}
void _zoomOut() {
final zoom = mapController?.getCamera().zoom ?? 12.0;
mapController?.animateCamera(zoom: zoom - 1);
}
}mapmetrics also ships a ready-made zoom control — MapControlButtons(showZoomInOutButton: true) — dropped straight into mapChildren. The hand-rolled version above is useful when you want full control over placement and styling.
Complete Navigation Panel
A full set of controls — zoom, compass, location, and reset — built on the real MapController API. Camera state (used to rotate the compass icon and show the zoom readout) is tracked through onEvent, since there is no onCameraMove callback on the widget itself:
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class FullNavigationScreen extends StatefulWidget {
@override
_FullNavigationScreenState createState() => _FullNavigationScreenState();
}
class _FullNavigationScreenState extends State<FullNavigationScreen> {
MapController? mapController;
MapCamera? currentCamera;
static final Position parisCenter = Position(2.3522, 48.8566); // lng, lat
@override
Widget build(BuildContext context) {
final bearing = currentCamera?.bearing ?? 0.0;
return Scaffold(
body: Stack(
children: [
// Map
MapMetricsView(
options: MapOptions(
initStyle: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
initCenter: parisCenter,
initZoom: 12.0,
),
onMapCreated: (controller) => mapController = controller,
onEvent: (event) {
if (event is MapEventMoveCamera) {
setState(() => currentCamera = event.camera);
}
},
),
// 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() {
final zoom = mapController?.getCamera().zoom ?? 12.0;
mapController?.animateCamera(zoom: zoom + 1);
}
void _zoomOut() {
final zoom = mapController?.getCamera().zoom ?? 12.0;
mapController?.animateCamera(zoom: zoom - 1);
}
void _resetNorth() {
mapController?.animateCamera(bearing: 0, pitch: 0);
}
void _goToMyLocation() async {
await mapController?.enableLocation();
await mapController?.trackLocation();
}
void _resetView() {
mapController?.animateCamera(
center: parisCenter,
zoom: 12.0,
bearing: 0,
pitch: 0,
);
}
}mapmetrics also ships a real compass widget, MapCompass (drop into mapChildren), which rotates and resets to north on tap automatically — you don't have to build the rotation logic yourself unless you want a fully custom look.
Control Positioning
| Position | Use Case |
|---|---|
| Top-right | Zoom controls, compass (most common) |
| Top-left | Search bar, back button |
| Bottom-right | Attribution, scale bar |
| Bottom-left | Zoom level, coordinates |
Next Steps
- Restrict Map Panning — Lock the camera to a region
- Multiple Geometries — Combine controls with markers and shapes
- Popup on Click — Interactive marker taps
Tip: Wrap your controls in a Container with a white background and boxShadow to match the look of standard map control panels — or use the SDK's own MapControlButtons, MapCompass, and MapScalebar widgets in mapChildren for a ready-made look.