Skip to content

Offset the Vanishing Point Using Padding in Flutter

This tutorial shows how to offset the map's center point using padding — so the map's focal point shifts to accommodate UI overlays like side panels, bottom sheets, or info cards without covering the area of interest.

Prerequisites

Before you begin, ensure you have:

Bottom Sheet with Map Padding

Shift the map center up when a bottom panel is shown:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class MapPaddingScreen extends StatefulWidget {
  @override
  _MapPaddingScreenState createState() => _MapPaddingScreenState();
}

class _MapPaddingScreenState extends State<MapPaddingScreen> {
  MapMetricsController? mapController;
  bool showPanel = false;
  final double panelHeight = 200.0;

  final LatLng target = LatLng(48.8584, 2.2945); // Eiffel Tower

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Map Padding')),
      body: Stack(
        children: [
          MapMetrics(
            styleUrl:
                'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
            onMapCreated: (MapMetricsController controller) {
              mapController = controller;
            },
            initialCameraPosition: CameraPosition(
              target: target,
              zoom: 15.0,
            ),
            // Apply padding to shift the vanishing point
            contentPadding: EdgeInsets.only(
              bottom: showPanel ? panelHeight : 0.0,
            ),
            markers: {
              Marker(
                markerId: MarkerId('target'),
                position: target,
                infoWindow: InfoWindow(title: 'Eiffel Tower'),
              ),
            },
          ),
          // Bottom panel
          if (showPanel)
            Positioned(
              bottom: 0,
              left: 0,
              right: 0,
              child: Container(
                height: panelHeight,
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius:
                      BorderRadius.vertical(top: Radius.circular(16)),
                  boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8)],
                ),
                child: Padding(
                  padding: EdgeInsets.all(20),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Expanded(
                            child: Text('Eiffel Tower',
                                style: TextStyle(
                                    fontSize: 20,
                                    fontWeight: FontWeight.bold)),
                          ),
                          IconButton(
                            icon: Icon(Icons.close),
                            onPressed: () => setState(() => showPanel = false),
                          ),
                        ],
                      ),
                      Text('Champ de Mars, 5 Av. Anatole France',
                          style: TextStyle(color: Colors.grey[600])),
                      SizedBox(height: 12),
                      Row(
                        children: [
                          Icon(Icons.star, color: Colors.amber, size: 18),
                          Text(' 4.7 (200K reviews)'),
                        ],
                      ),
                      Spacer(),
                      SizedBox(
                        width: double.infinity,
                        child: ElevatedButton(
                          onPressed: () {},
                          child: Text('Get Directions'),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
        ],
      ),
      floatingActionButton: showPanel
          ? null
          : FloatingActionButton(
              onPressed: () {
                setState(() => showPanel = true);
                // Re-center with padding applied
                mapController?.animateCamera(
                  CameraUpdate.newLatLng(target),
                );
              },
              child: Icon(Icons.info),
            ),
    );
  }
}

Side Panel Padding

Shift the map center to the right when a left panel is open:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class SidePanelPaddingScreen extends StatefulWidget {
  @override
  _SidePanelPaddingScreenState createState() =>
      _SidePanelPaddingScreenState();
}

class _SidePanelPaddingScreenState extends State<SidePanelPaddingScreen> {
  MapMetricsController? mapController;
  bool showSidePanel = false;
  final double panelWidth = 250.0;

  final List<Map<String, dynamic>> places = [
    {'name': 'Eiffel Tower', 'lat': 48.8584, 'lng': 2.2945},
    {'name': 'Louvre Museum', 'lat': 48.8606, 'lng': 2.3376},
    {'name': 'Notre-Dame', 'lat': 48.8530, 'lng': 2.3499},
    {'name': 'Sacre-Coeur', 'lat': 48.8867, 'lng': 2.3431},
    {'name': 'Arc de Triomphe', 'lat': 48.8738, 'lng': 2.2950},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Side Panel Padding'),
        leading: IconButton(
          icon: Icon(showSidePanel ? Icons.menu_open : Icons.menu),
          onPressed: () => setState(() => showSidePanel = !showSidePanel),
        ),
      ),
      body: Row(
        children: [
          // Side panel
          if (showSidePanel)
            Container(
              width: panelWidth,
              color: Colors.white,
              child: ListView.builder(
                itemCount: places.length,
                itemBuilder: (context, i) {
                  final place = places[i];
                  return ListTile(
                    leading: Icon(Icons.location_on, color: Colors.blue),
                    title: Text(place['name']),
                    onTap: () {
                      mapController?.animateCamera(
                        CameraUpdate.newLatLngZoom(
                          LatLng(place['lat'], place['lng']),
                          15.0,
                        ),
                      );
                    },
                  );
                },
              ),
            ),
          // Map
          Expanded(
            child: MapMetrics(
              styleUrl:
                  'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (MapMetricsController controller) {
                mapController = controller;
              },
              initialCameraPosition: CameraPosition(
                target: LatLng(48.860, 2.320),
                zoom: 13.0,
              ),
              // Padding shifts the logical center
              contentPadding: EdgeInsets.only(
                left: showSidePanel ? panelWidth * 0.5 : 0.0,
              ),
              markers: places.map((p) {
                return Marker(
                  markerId: MarkerId(p['name']),
                  position: LatLng(p['lat'], p['lng']),
                  infoWindow: InfoWindow(title: p['name']),
                );
              }).toSet(),
            ),
          ),
        ],
      ),
    );
  }
}

Padding Options

PaddingEffectUse Case
bottomShifts center upBottom sheets, info panels
topShifts center downTop search bars
leftShifts center rightLeft sidebars, drawers
rightShifts center leftRight panels
CombinedShifts to available spaceComplex layouts

Next Steps


Tip: Map padding keeps the target location visible and centered in the available space, not the full screen. This is essential for apps like Uber or Google Maps where a bottom sheet covers half the screen but the pin should still be centered in the visible portion.