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:
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
| 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
- Set Pitch and Bearing — Use compass to control 3D view
- Locate the User — GPS location with controls
- Fullscreen Map — Combine controls with fullscreen
Tip: Wrap your controls in a Container with a white background and boxShadow to match the look of standard map control panels.