Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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:

There is no BitmapDescriptor, Marker(markerId:, position:, icon:), or markers: set on the map widget in this SDK. Instead, Marker.child (used with WidgetLayer in MapMetricsView.mapChildren) takes any Flutter Widget directly — so Image.asset / Image.network widgets 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:

yaml
flutter:
  assets:
    - assets/images/

Then reference it directly with Image.asset as the marker's child:

dart
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:

dart
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:

dart
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:

dart
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

TipDetails
SizeKeep Marker.size small (48x48 to 96x96) for performance, matching the child widget's dimensions
FormatUse PNG with transparency for best results
ResolutionProvide 2x/3x asset variants; Flutter picks the right one for the device pixel ratio automatically
InteractionSet WidgetLayer(allowInteraction: true) if markers need GestureDetector taps
RebuildsStore your List<Marker> in state and setState() when it changes — no async bitmap decoding to wait on

Next Steps


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.