Add Image Markers in Flutter
Static pins render better with MarkerLayer
This page uses WidgetLayer, which repositions each marker in Dart every frame — it lags a frame while panning, ignores tilt and bearing, and does not scale past a few dozen markers. If your markers are plain pins with no Flutter content or gestures, use MarkerLayer instead; see Markers and Annotations.
This tutorial shows how to use custom images as map markers instead of the default pin icons.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
There is no
BitmapDescriptor,Marker(markerId:, position:, icon:), ormarkers:set on the map widget in this SDK. Instead,Marker.child(used withWidgetLayerinMapMetricsView.mapChildren) takes any FlutterWidgetdirectly — soImage.asset/Image.networkwidgets work as markers with no bitmap-loading step at all.
Using Asset Images
First, add your marker image to your project's assets/images/ folder and register it in pubspec.yaml:
flutter:
assets:
- assets/images/Then reference it directly with Image.asset as the marker's child:
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class ImageMarkerScreen extends StatefulWidget {
@override
_ImageMarkerScreenState createState() => _ImageMarkerScreenState();
}
class _ImageMarkerScreenState extends State<ImageMarkerScreen> {
MapController? mapController;
late final List<Marker> markers = [
Marker(
point: Position(2.3522, 48.8566), // Paris (lng, lat)
size: const Size(48, 48),
child: GestureDetector(
onTap: () => debugPrint('My Favorite Café — Best coffee in Paris'),
child: Image.asset('assets/images/custom_pin.png'),
),
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Image Markers')),
body: MapMetricsView(
options: MapOptions(
initCenter: Position(2.3522, 48.8566), // lng, lat
initZoom: 14.0,
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
),
onMapCreated: (controller) => mapController = controller,
mapChildren: [WidgetLayer(markers: markers, allowInteraction: true)],
),
);
}
}Because Image.asset is already a widget, there's no separate load-then-setState step — the marker list can be a plain final field.
Multiple Image Markers from Data
Load several markers with different custom icons from a data list:
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class MultiImageMarkersScreen extends StatefulWidget {
@override
_MultiImageMarkersScreenState createState() => _MultiImageMarkersScreenState();
}
class _MultiImageMarkersScreenState extends State<MultiImageMarkersScreen> {
MapController? mapController;
final List<Map<String, dynamic>> places = [
{
'id': 'restaurant',
'name': 'Le Bistrot',
'snippet': 'French restaurant',
'point': Position(2.3500, 48.8580), // lng, lat
'icon': 'assets/images/restaurant_pin.png',
},
{
'id': 'hotel',
'name': 'Grand Hotel',
'snippet': '5-star hotel',
'point': Position(2.3480, 48.8550),
'icon': 'assets/images/hotel_pin.png',
},
{
'id': 'museum',
'name': 'Art Gallery',
'snippet': 'Modern art museum',
'point': Position(2.3550, 48.8540),
'icon': 'assets/images/museum_pin.png',
},
];
late final List<Marker> markers = [
for (final place in places)
Marker(
point: place['point'] as Position,
size: const Size(48, 48),
child: GestureDetector(
onTap: () =>
debugPrint('${place['name']} — ${place['snippet']}'),
child: Image.asset(place['icon'] as String),
),
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Places in Paris')),
body: MapMetricsView(
options: MapOptions(
initCenter: Position(2.3510, 48.8560), // lng, lat
initZoom: 15.0,
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
),
onMapCreated: (controller) => mapController = controller,
mapChildren: [WidgetLayer(markers: markers, allowInteraction: true)],
),
);
}
}Using Network Images
Load marker icons from a URL — Image.network handles the fetch and caching itself, so there is no fromNetworkImage loading step to await:
Marker _networkMarker() => Marker(
point: Position(2.3400, 48.8600), // Network Icon (lng, lat)
size: const Size(48, 48),
child: GestureDetector(
onTap: () => debugPrint('Network Icon'),
child: Image.network('https://example.com/marker-icon.png'),
),
);
// Usage: append to your markers list and setState() to trigger a rebuild
setState(() {
markers = [...markers, _networkMarker()];
});Create Markers from Widgets
Because Marker.child already accepts any widget, there is no BitmapDescriptor.fromWidget step to rasterize a widget into a bitmap first — build the widget and pass it straight in:
Widget _pillMarker(String label, Color color) => Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2)),
],
),
child: Text(
label,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12),
),
);
// Usage
final marker = Marker(
point: Position(2.3522, 48.8566), // lng, lat
size: const Size(72, 32),
child: _pillMarker('Café', Colors.brown),
);Image Marker Best Practices
| Tip | Details |
|---|---|
| Size | Keep Marker.size small (48x48 to 96x96) for performance, matching the child widget's dimensions |
| Format | Use PNG with transparency for best results |
| Resolution | Provide 2x/3x asset variants; Flutter picks the right one for the device pixel ratio automatically |
| Interaction | Set WidgetLayer(allowInteraction: true) if markers need GestureDetector taps |
| Rebuilds | Store your List<Marker> in state and setState() when it changes — no async bitmap decoding to wait on |
Next Steps
- Markers and Annotations — Default markers with color options
- Draggable Marker — Make image markers draggable
- Add a Popup — Show popups on image marker taps
Tip: Because Marker.child is a normal widget, Image.asset/Image.network load and cache exactly like anywhere else in Flutter — there's no marker-specific async bitmap step to manage or cache yourself.