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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
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
| Padding | Effect | Use Case |
|---|---|---|
bottom | Shifts center up | Bottom sheets, info panels |
top | Shifts center down | Top search bars |
left | Shifts center right | Left sidebars, drawers |
right | Shifts center left | Right panels |
| Combined | Shifts to available space | Complex layouts |
Next Steps
- Display a Popup — Popup patterns
- Fullscreen Map — Full screen map view
- Fit to Bounding Box — Fit content with padding
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.