Restrict Map Panning in Flutter
This tutorial shows how to limit the map to a specific geographic area so users cannot pan or zoom outside of it.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Set Max Bounds
Restrict the map to only show Paris:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class RestrictPanningScreen extends StatefulWidget {
@override
_RestrictPanningScreenState createState() => _RestrictPanningScreenState();
}
class _RestrictPanningScreenState extends State<RestrictPanningScreen> {
MapMetricsController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Restricted to Paris')),
body: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) {
mapController = controller;
// Set bounds to restrict panning
controller.setMaxBounds(
LatLngBounds(
southwest: LatLng(48.800, 2.220),
northeast: LatLng(48.920, 2.470),
),
);
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 12.0,
),
minMaxZoomPreference: MinMaxZoomPreference(10.0, 18.0),
),
);
}
}Users can pan and zoom within Paris but the map will bounce back if they try to go outside the boundary.
Restrict with Min/Max Zoom
Combine bounds with zoom limits:
dart
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) {
controller.setMaxBounds(
LatLngBounds(
southwest: LatLng(48.800, 2.220),
northeast: LatLng(48.920, 2.470),
),
);
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 12.0,
),
minMaxZoomPreference: MinMaxZoomPreference(
10.0, // Can't zoom out further than this
18.0, // Can't zoom in further than this
),
)Toggle Bounds On/Off
Let users switch between restricted and free panning:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class ToggleBoundsScreen extends StatefulWidget {
@override
_ToggleBoundsScreenState createState() => _ToggleBoundsScreenState();
}
class _ToggleBoundsScreenState extends State<ToggleBoundsScreen> {
MapMetricsController? mapController;
bool isRestricted = true;
final LatLngBounds parisBounds = LatLngBounds(
southwest: LatLng(48.800, 2.220),
northeast: LatLng(48.920, 2.470),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(isRestricted ? 'Restricted' : 'Free Panning'),
actions: [
TextButton.icon(
onPressed: _toggleBounds,
icon: Icon(
isRestricted ? Icons.lock : Icons.lock_open,
color: Colors.white,
),
label: Text(
isRestricted ? 'Unlock' : 'Lock',
style: TextStyle(color: Colors.white),
),
),
],
),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) {
mapController = controller;
if (isRestricted) {
controller.setMaxBounds(parisBounds);
}
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 12.0,
),
),
// Bounds indicator
if (isRestricted)
Positioned(
bottom: 24,
left: 16,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.9),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.lock, size: 16, color: Colors.white),
SizedBox(width: 6),
Text(
'Map restricted to Paris',
style: TextStyle(color: Colors.white, fontSize: 13),
),
],
),
),
),
],
),
);
}
void _toggleBounds() {
setState(() {
isRestricted = !isRestricted;
});
if (isRestricted) {
mapController?.setMaxBounds(parisBounds);
} else {
mapController?.setMaxBounds(null); // Remove bounds
}
}
}Switchable Regions
Let users select which region to restrict to:
dart
final Map<String, LatLngBounds> regions = {
'Paris': LatLngBounds(
southwest: LatLng(48.800, 2.220),
northeast: LatLng(48.920, 2.470),
),
'Manhattan': LatLngBounds(
southwest: LatLng(40.700, -74.020),
northeast: LatLng(40.800, -73.930),
),
'Central London': LatLngBounds(
southwest: LatLng(51.490, -0.180),
northeast: LatLng(51.530, -0.070),
),
};
void _switchRegion(String regionName) {
final bounds = regions[regionName];
if (bounds == null) return;
mapController?.setMaxBounds(bounds);
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 50.0),
);
}Restriction Options
| Option | Description |
|---|---|
setMaxBounds(bounds) | Restrict panning to a bounding box |
setMaxBounds(null) | Remove bounds restriction |
MinMaxZoomPreference(min, max) | Restrict zoom levels |
Next Steps
- Fit to Bounding Box — Zoom to fit a specific area
- Disable Scroll Zoom — Control zoom gestures
- Toggle Interactions — Fine-grained gesture control
Tip: Combine setMaxBounds with MinMaxZoomPreference to prevent users from zooming out far enough to see the bounds edges, giving a seamless restricted experience.