Zooming and Panning in Flutter Maps (SfMaps)

19 Oct 202224 minutes to read

It is possible to zoom in and out for any layer to take a closer look at a specific region by pinching the map, scrolling the mouse wheel or track pad, or using the toolbar on the web. Pan the map to navigate across the regions. You can also customize the zoom level and the center point of the initial rendering.

The procedure for zooming and panning for both layers is very similar.

Shape layer

late MapZoomPanBehavior _zoomPanBehavior;
late MapShapeSource _dataSource;

@override
void initState() {
  super.initState();
  _dataSource = MapShapeSource.asset(
    'assets/world_map.json',
    shapeDataField: 'continent',
  );
  _zoomPanBehavior = MapZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return Scaffold(
        body: SfMaps(
            layers: [
                MapShapeLayer(
                    source: _dataSource,
                    zoomPanBehavior: _zoomPanBehavior,
                ),
            ],
        ),
    );
}

Tile layer

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

Bing maps aerial default

Customizing the center latitude and longitude

The MapZoomPanBehavior.focalLatLng is the focal point of the map layer based on which zooming happens. It represents the focal latitude and longitude position of the map layer. You can also get the current focalLatLng after interaction using the MapZoomPanBehavior.focalLatLng property.

To enable panning, set the instance of MapZoomPanBehavior to MapTileLayer.zoomPanBehavior. By default, it will be true.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior(
        focalLatLng: MapLatLng(27.1751, 50.0421),
    );
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

Bing maps aerial focalLatLng

Update the center latitude and longitude programmatically

You can change the center latitude and longitude of the shape layer programmatically using the MapZoomPanBehavior.focalLatLng property.

NOTE

It is applicable for both shape layer and tile layer.

late MapZoomPanBehavior _zoomPanBehavior;
late MapShapeSource _dataSource;

@override
void initState() {
  super.initState();
  _dataSource = MapShapeSource.asset(
     'assets/world_map.json',
      shapeDataField: 'continent',
  );

  _zoomPanBehavior = MapZoomPanBehavior(
    focalLatLng: MapLatLng(27.1751, 78.0421),
    zoomLevel: 4,
  );
}

@override
Widget build(BuildContext context) {
  return Scaffold(
     body: Column(
        children: [
          Container(
            height: 600,
            child: SfMaps(
              layers: [
                MapShapeLayer(
                  source: _dataSource,
                  zoomPanBehavior: _zoomPanBehavior,
                ),
              ],
            ),
          ),
          SizedBox(height: 20),
          RaisedButton(
            child: Text('Change focalLatLng'),
            onPressed: () {
               _zoomPanBehavior.focalLatLng = MapLatLng(56.1304, -106.3468);
            },
          ),
        ],
     ),
  );
}

Customizing the zoom level

You can set the current zoom level of the map layer by using MapZoomPanBehavior.zoomLevel property.

The default MapZoomPanBehavior.zoomLevel value is 1 which will show the whole map in the viewport for MapShapeLayer and the possible bounds for the MapShapeLayer based on the MapZoomPanBehavior.focalLatLng. You can also get the current zoom level after interaction using the MapZoomPanBehavior.zoomLevel property.

To enable zooming, set the instance of MapZoomPanBehavior to MapTileLayer.zoomPanBehavior. By default, it will be true.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior(
        focalLatLng: MapLatLng(27.1751, 78.0421),
        zoomLevel: 5,
    );
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

Bing maps aerial zoomlevel

Update the zoom level programmatically

You can change the zoom level of the shape layer programmatically using the MapZoomPanBehavior.zoomLevel property.

NOTE

It is applicable for both shape layer and tile layer.

late MapZoomPanBehavior _zoomPanBehavior;
late MapShapeSource _dataSource;

@override
void initState() {
  _dataSource = MapShapeSource.asset(
     'assets/world_map.json',
     shapeDataField: 'continent',
  );
  _zoomPanBehavior = MapZoomPanBehavior(
     focalLatLng: MapLatLng(27.1751, 78.0421),
     zoomLevel: 2,
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      children: [
        Container(
          height: 600,
          child: SfMaps(
            layers: [
              MapShapeLayer(
                source: _dataSource,
                zoomPanBehavior: _zoomPanBehavior,
              ),
            ],
          ),
        ),
        SizedBox(height: 20),
        RaisedButton(
          child: Text('Change zoomLevel'),
          onPressed: () {
            _zoomPanBehavior.zoomLevel = 7;
          },
        ),
      ],
    ),
  );
}

Customizing min and max zoom level

You can set the min and max zoom level of the map layer by setting the value to MapZoomPanBehavior.minZoomLevel and MapZoomPanBehavior.maxZoomLevel properties. The minimum and maximum zooming levels can be restricted using these properties respectively. The default values of MapZoomPanBehavior.minZoomLevel and MapZoomPanBehavior.maxZoomLevel are 1 and 15 respectively.

However, for MapTileLayer, MapZoomPanBehavior.maxZoomLevel may slightly vary depends on the providers. Kindly check the respective official website of the map tile providers to know about the maximum zoom level it supports.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior(
        focalLatLng: MapLatLng(27.1751, 78.0421),
        zoomLevel: 5,
        minZoomLevel: 3,
        maxZoomLevel: 10,
    );
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

Double tap zooming

Double tap zooming can be enabled using enableDoubleTapZooming property. The default value of the enableDoubleTapZooming is false.

NOTE

It is applicable for both tile layer and shape layer.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
  _zoomPanBehavior = MapZoomPanBehavior(enableDoubleTapZooming: true);
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SfMaps(
       layers: [
         MapTileLayer(
           urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
           zoomPanBehavior: _zoomPanBehavior,
         ),
       ],
     ),
  );
}

Double tap zooming

Toolbar

The toolbar can be used to perform zoom in, zoom out, and reset operations in the web and desktop platforms. By default, the MapZoomPanBehavior.showToolbar property is true.

Toolbar customization

You can customize the toolbar items using the following properties:

  • Position - Used to position the toolbar item at the four corners of the map visual bounds using the position property. The default value of the position property is MapToolbarPosition.topRight. The possible values are topRight, topLeft, bottomLeft, and bottomRight.
  • Icon color - used to set color to the three icons using the iconColor property.
  • Item background color - Used to set background color of the toolbar icons using the itemBackgroundColor property.
  • Item hover color - Used to set color while hover over the icon using the itemHoverColor property.
late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
  _zoomPanBehavior = MapZoomPanBehavior(
    focalLatLng: MapLatLng(27.1751, 78.0421),
    zoomLevel: 3,
    showToolbar: true,
    toolbarSettings: MapToolbarSettings(
      position: MapToolbarPosition.topLeft,
      iconColor: Colors.red,
      itemBackgroundColor: Colors.green,
      itemHoverColor: Colors.blue,
    ),
  );
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(
      height: 350,
      width: 350,
      child: SfMaps(
        layers: [
          MapTileLayer(
            urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
            zoomPanBehavior: _zoomPanBehavior,
          ),
        ],
      ),
    ),
  );
}

Toolbar customization

Zooming callback

Whenever zooming happens, this callback is called. If it returns false, zooming will not happen.

MapZoomDetails contains following properties.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior()
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                            onWillZoom: (MapZoomDetails detail) {
                                return true;
                            },
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

Panning callback

Whenever panning happens, this callback is called. If it returns false, panning will not happen.

MapPanDetails contains following properties.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                            onWillPan: (MapPanDetails detail) {
                                return true;
                            },
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

Overriding the zoom pan behavior

Zooming

Whenever zooming happens, this method is called. Subclasses can override this method to do any custom operations based on the details provided in the MapZoomDetails.
MapZoomDetails contains following properties,

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = _CustomZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

class _CustomZoomPanBehavior extends MapZoomPanBehavior {

  @override
  void onZooming(MapZoomDetails details) {
    super.onZooming(details);
    // Add the code here.
  }
}

NOTE

  • When super.onZooming(details) is not called, zooming will not happen.

Panning

Whenever panning happens, this method is called. Subclasses can override this method to do any custom operations based on the details provided in the MapPanDetails.

MapPanDetails contains following properties,

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = _CustomZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

class _CustomZoomPanBehavior extends MapZoomPanBehavior {

  @override
  void onPanning(MapPanDetails details) {
    super.onPanning(details);
    // Add the code here
  }
}

NOTE

  • When super.onPanning(details) is not called, panning will not happen.

Reset

You can reset the map to the MapZoomPanBehavior.minZoomLevel by calling this method.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = MapZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return Column(
        children: [
            FutureBuilder(
                future: getBingUrlTemplate(
                    'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
                builder: (context, snapshot) {
                    if (snapshot.hasData) {
                        return SfMaps(
                            layers: [
                                MapTileLayer(
                                    urlTemplate: snapshot.data as String,
                                    zoomPanBehavior: _zoomPanBehavior,
                                ),
                            ],
                        );
                    }
                    return CircularProgressIndicator();
                },
            ),
            FlatButton(
                onPressed: () {
                    _zoomPanBehavior.reset();
                },
                child: Text('Reset Zoom Level'),
            ),
        ],
    );
}

HandleEvent

You can override this method to handle pointer events that hit this render object.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = _CustomZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

class _CustomZoomPanBehavior extends MapZoomPanBehavior {

  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    super.handleEvent(event, entry);
    // Add the code here
  }
}

Paint

You can paint this render object into the given context at the given offset.

late MapZoomPanBehavior _zoomPanBehavior;

@override
void initState() {
    super.initState();
    _zoomPanBehavior = _CustomZoomPanBehavior();
}

@override
Widget build(BuildContext context) {
    return FutureBuilder(
        future: getBingUrlTemplate(
            'https://dev.virtualearth.net/REST/V1/Imagery/Metadata/AerialWithLabels?output=json&uriScheme=https&include=ImageryProviders&key=YOUR_KEY'),
        builder: (context, snapshot) {
            if (snapshot.hasData) {
                return SfMaps(
                    layers: [
                        MapTileLayer(
                            urlTemplate: snapshot.data as String,
                            zoomPanBehavior: _zoomPanBehavior,
                        ),
                    ],
                );
           }
        return CircularProgressIndicator();
        }
    );
}

class _CustomZoomPanBehavior extends MapZoomPanBehavior {

  @override
  void paint(PaintingContext context, Offset offset) {
    super.paint(context, offset);
    // Add the code here
  }
 
}

NOTE

You can refer to our Flutter Maps feature tour page for its groundbreaking feature representations. You can also explore our Flutter Maps Zoom & Pan example that shows how to configure a Maps in Flutter.