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 a Pattern to a Polygon in Flutter

This tutorial shows how to create patterned polygon fills — stripes, dashed/dotted borders, or custom patterns — instead of a plain solid color.

Prerequisites

Before you begin, ensure you have:

There is no Polygon/Polyline/PatternItem type or polygons:/polylines: set on the map widget in this SDK. Polygon fills and outlines are FillStyleLayer / LineStyleLayer over a GeoJsonSource, styled with plain MapLibre style spec paint properties. Real tiled pattern fills use fill-pattern, which references an image registered with StyleController.addImage — this replaces the "draw dozens of overlapping polylines to fake stripes" workaround with the actual supported mechanism.

Pattern Fill Using fill-pattern

Generate a small striped tile once, register it as an image, then reference it from fill-pattern:

dart
import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class StripedPolygonScreen extends StatefulWidget {
  @override
  _StripedPolygonScreenState createState() => _StripedPolygonScreenState();
}

class _StripedPolygonScreenState extends State<StripedPolygonScreen> {
  MapController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Striped Polygon')),
      body: MapMetricsView(
        options: MapOptions(
          initCenter: Position(2.34, 48.85), // lng, lat
          initZoom: 12.0,
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
        ),
        onMapCreated: (MapController controller) {
          mapController = controller;
        },
        onStyleLoaded: (StyleController style) {
          _addStripedPolygon(style);
        },
      ),
    );
  }

  /// Draw a small repeatable diagonal-stripe tile.
  Future<Uint8List> _generateStripeTile({
    int size = 32,
    Color stripeColor = Colors.blue,
  }) async {
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.toDouble(), size.toDouble()),
      Paint()..color = stripeColor.withOpacity(0.15),
    );
    final linePaint = Paint()
      ..color = stripeColor.withOpacity(0.6)
      ..strokeWidth = 3;
    for (double x = -size.toDouble(); x < size * 2; x += 8) {
      canvas.drawLine(
        Offset(x, size.toDouble()),
        Offset(x + size, 0),
        linePaint,
      );
    }
    final picture = recorder.endRecording();
    final image = await picture.toImage(size, size);
    final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
    return bytes!.buffer.asUint8List();
  }

  Future<void> _addStripedPolygon(StyleController style) async {
    final tile = await _generateStripeTile();
    await style.addImage('stripe-pattern', tile);

    final geoJson = {
      'type': 'Feature',
      'properties': {},
      'geometry': {
        'type': 'Polygon',
        'coordinates': [
          [
            [2.28, 48.88],
            [2.40, 48.88],
            [2.40, 48.82],
            [2.28, 48.82],
            [2.28, 48.88], // close
          ]
        ],
      },
    };

    await style.addSource(
      GeoJsonSource(id: 'region-source', data: jsonEncode(geoJson)),
    );

    await style.addLayer(
      const FillStyleLayer(
        id: 'region_fill',
        sourceId: 'region-source',
        paint: {'fill-pattern': 'stripe-pattern'},
      ),
    );

    await style.addLayer(
      const LineStyleLayer(
        id: 'region_outline',
        sourceId: 'region-source',
        paint: {'line-color': '#3b82f6', 'line-width': 2.0},
      ),
    );
  }
}

Dashed Border Polygon

A dashed outline is a line-dasharray on the border's LineStyleLayer — no separate polyline pattern type needed:

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

class DashedPolygonScreen extends StatefulWidget {
  @override
  _DashedPolygonScreenState createState() => _DashedPolygonScreenState();
}

class _DashedPolygonScreenState extends State<DashedPolygonScreen> {
  MapController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dashed Border Polygon')),
      body: MapMetricsView(
        options: MapOptions(
          initCenter: Position(2.335, 48.855), // lng, lat
          initZoom: 13.0,
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
        ),
        onMapCreated: (MapController controller) {
          mapController = controller;
        },
        onStyleLoaded: (StyleController style) {
          _addDashedPolygon(style);
        },
      ),
    );
  }

  Future<void> _addDashedPolygon(StyleController style) async {
    final geoJson = {
      'type': 'Feature',
      'properties': {},
      'geometry': {
        'type': 'Polygon',
        'coordinates': [
          [
            [2.300, 48.870],
            [2.370, 48.870],
            [2.370, 48.840],
            [2.300, 48.840],
            [2.300, 48.870],
          ]
        ],
      },
    };

    await style.addSource(
      GeoJsonSource(id: 'dashed-area', data: jsonEncode(geoJson)),
    );

    await style.addLayer(
      const FillStyleLayer(
        id: 'dashed_area_fill',
        sourceId: 'dashed-area',
        paint: {'fill-color': '#f97316', 'fill-opacity': 0.15},
      ),
    );

    await style.addLayer(
      const LineStyleLayer(
        id: 'dashed_border',
        sourceId: 'dashed-area',
        paint: {
          'line-color': '#f97316',
          'line-width': 3.0,
          'line-dasharray': [1.5, 1.0],
        },
      ),
    );
  }
}

Multiple Pattern Styles

Show several polygons with different visual treatments. A "dotted" look is a short dash with a round cap:

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

class MultiPatternScreen extends StatefulWidget {
  @override
  _MultiPatternScreenState createState() => _MultiPatternScreenState();
}

class _MultiPatternScreenState extends State<MultiPatternScreen> {
  MapController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Pattern Styles')),
      body: Stack(
        children: [
          MapMetricsView(
            options: MapOptions(
              initCenter: Position(2.335, 48.855), // lng, lat
              initZoom: 12.5,
              initStyle:
                  'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
            ),
            onMapCreated: (MapController controller) {
              mapController = controller;
            },
            onStyleLoaded: (StyleController style) {
              _addPatternZones(style);
            },
          ),
          // Legend
          Positioned(
            bottom: 16,
            left: 16,
            child: Card(
              child: Padding(
                padding: EdgeInsets.all(10),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text('Zones',
                        style: TextStyle(
                            fontWeight: FontWeight.bold, fontSize: 12)),
                    _legendRow(Colors.blue, 'A: Solid fill'),
                    _legendRow(Colors.green, 'B: Thick border'),
                    _legendRow(Colors.red, 'C: Transparent'),
                    _legendRow(Colors.purple, 'D: Dotted border'),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _addPatternZones(StyleController style) async {
    // Zone A: Solid fill
    await style.addSource(
      GeoJsonSource(
        id: 'zone-a',
        data: jsonEncode({
          'type': 'Feature',
          'properties': {},
          'geometry': {
            'type': 'Polygon',
            'coordinates': [
              [
                [2.280, 48.870], [2.320, 48.870],
                [2.320, 48.855], [2.280, 48.855], [2.280, 48.870],
              ]
            ],
          },
        }),
      ),
    );
    await style.addLayer(
      const FillStyleLayer(
        id: 'zone_a_fill',
        sourceId: 'zone-a',
        paint: {'fill-color': '#3b82f6', 'fill-opacity': 0.3},
      ),
    );
    await style.addLayer(
      const LineStyleLayer(
        id: 'zone_a_outline',
        sourceId: 'zone-a',
        paint: {'line-color': '#3b82f6', 'line-width': 2.0},
      ),
    );

    // Zone B: Light fill with thick border
    await style.addSource(
      GeoJsonSource(
        id: 'zone-b',
        data: jsonEncode({
          'type': 'Feature',
          'properties': {},
          'geometry': {
            'type': 'Polygon',
            'coordinates': [
              [
                [2.330, 48.870], [2.370, 48.870],
                [2.370, 48.855], [2.330, 48.855], [2.330, 48.870],
              ]
            ],
          },
        }),
      ),
    );
    await style.addLayer(
      const FillStyleLayer(
        id: 'zone_b_fill',
        sourceId: 'zone-b',
        paint: {'fill-color': '#22c55e', 'fill-opacity': 0.1},
      ),
    );
    await style.addLayer(
      const LineStyleLayer(
        id: 'zone_b_outline',
        sourceId: 'zone-b',
        paint: {'line-color': '#22c55e', 'line-width': 4.0},
      ),
    );

    // Zone C: Very transparent
    await style.addSource(
      GeoJsonSource(
        id: 'zone-c',
        data: jsonEncode({
          'type': 'Feature',
          'properties': {},
          'geometry': {
            'type': 'Polygon',
            'coordinates': [
              [
                [2.280, 48.850], [2.320, 48.850],
                [2.320, 48.835], [2.280, 48.835], [2.280, 48.850],
              ]
            ],
          },
        }),
      ),
    );
    await style.addLayer(
      const FillStyleLayer(
        id: 'zone_c_fill',
        sourceId: 'zone-c',
        paint: {'fill-color': '#ef4444', 'fill-opacity': 0.05},
      ),
    );
    await style.addLayer(
      const LineStyleLayer(
        id: 'zone_c_outline',
        sourceId: 'zone-c',
        paint: {'line-color': '#ef4444', 'line-width': 2.0},
      ),
    );

    // Zone D: Dotted border (short dash + round cap)
    await style.addSource(
      GeoJsonSource(
        id: 'zone-d',
        data: jsonEncode({
          'type': 'Feature',
          'properties': {},
          'geometry': {
            'type': 'Polygon',
            'coordinates': [
              [
                [2.330, 48.850], [2.370, 48.850],
                [2.370, 48.835], [2.330, 48.835], [2.330, 48.850],
              ]
            ],
          },
        }),
      ),
    );
    await style.addLayer(
      const LineStyleLayer(
        id: 'zone_d_dotted_border',
        sourceId: 'zone-d',
        layout: {'line-cap': 'round'},
        paint: {
          'line-color': '#a855f7',
          'line-width': 3.0,
          'line-dasharray': [0.1, 1.5],
        },
      ),
    );
  }

  Widget _legendRow(Color color, String label) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 1),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
              width: 14,
              height: 14,
              decoration: BoxDecoration(
                color: color.withOpacity(0.3),
                border: Border.all(color: color, width: 1.5),
              )),
          SizedBox(width: 6),
          Text(label, style: TextStyle(fontSize: 11)),
        ],
      ),
    );
  }
}

Pattern Techniques

TechniqueMethodBest For
Solid fill + borderFillStyleLayer fill-color/fill-opacity + LineStyleLayer line-colorDefault zones
Tiled pattern fillFillStyleLayer fill-pattern referencing an addImage'd tileRestricted / textured zones
Dashed borderLineStyleLayer line-dasharray, e.g. [1.5, 1.0]Boundaries, limits
Dotted borderLineStyleLayer line-dasharray with a short dash + line-cap: roundProposed areas
TransparencyLow fill-opacityBackground regions

Next Steps


Tip: Combine a semi-transparent FillStyleLayer with a LineStyleLayer using line-dasharray for a professional "planned area" or "restricted zone" look. Reserve fill-pattern tile images for cases where a repeating texture (stripes, crosshatch, dots) genuinely needs to render at the source pixel level, since it costs an extra addImage call and a raster asset to design.