Skip to content

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:

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

OptionDescription
setMaxBounds(bounds)Restrict panning to a bounding box
setMaxBounds(null)Remove bounds restriction
MinMaxZoomPreference(min, max)Restrict zoom levels

Next Steps


Tip: Combine setMaxBounds with MinMaxZoomPreference to prevent users from zooming out far enough to see the bounds edges, giving a seamless restricted experience.