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

Draw a Gradient Line in Flutter

This tutorial shows how to draw a line with a color gradient along its length — useful for showing elevation, speed, or progress along a route.

Prerequisites

Before you begin, ensure you have:

Gradient Line Using Multiple Segments

The real MapMetrics Flutter API's PolylineLayer takes a single color for all the lines it draws, just like the fictional API this page used to describe — so the technique is the same: simulate a gradient by breaking the route into short segments and giving each segment its own PolylineLayer with a progressively changing color. layers: accepts a List<Layer>, so you pass one PolylineLayer per segment:

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

class GradientLineScreen extends StatefulWidget {
  @override
  _GradientLineScreenState createState() => _GradientLineScreenState();
}

class _GradientLineScreenState extends State<GradientLineScreen> {
  MapController? mapController;

  // Route through European capitals — Position(lng, lat), GeoJSON order
  final List<Position> routePoints = [
    Position(-3.7038, 40.4168), // Madrid
    Position(2.3522, 48.8566),  // Paris
    Position(13.405, 52.52),    // Berlin
    Position(16.3738, 48.2082), // Vienna
    Position(28.9784, 41.0082), // Istanbul
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Gradient Line')),
      body: MapMetricsView(
        options: MapOptions(
          initCenter: Position(12.0, 47.0),
          initZoom: 4,
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
        ),
        onMapCreated: (MapController controller) {
          mapController = controller;
        },
        layers: _buildGradientLayers(),
      ),
    );
  }

  /// Build one PolylineLayer per segment so each can have its own color
  List<Layer> _buildGradientLayers() {
    final layers = <Layer>[];
    final segmentCount = routePoints.length - 1;

    for (int i = 0; i < segmentCount; i++) {
      // Calculate color at this position (blue -> purple -> red)
      final t = i / segmentCount;
      final color = Color.lerp(Colors.blue, Colors.red, t)!;

      layers.add(
        PolylineLayer(
          polylines: [
            LineString(coordinates: [routePoints[i], routePoints[i + 1]]),
          ],
          color: color,
          width: 5,
        ),
      );
    }

    return layers;
  }
}

Smooth Gradient with Interpolated Points

For a smoother gradient, interpolate extra points between waypoints. This produces many more segments — each still its own PolylineLayer — so keep the step count reasonable (see the performance note below):

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

class SmoothGradientLineScreen extends StatefulWidget {
  @override
  _SmoothGradientLineScreenState createState() =>
      _SmoothGradientLineScreenState();
}

class _SmoothGradientLineScreenState extends State<SmoothGradientLineScreen> {
  MapController? mapController;

  final List<Position> waypoints = [
    Position(-3.7038, 40.4168), // Madrid
    Position(2.3522, 48.8566),  // Paris
    Position(13.405, 52.52),    // Berlin
    Position(16.3738, 48.2082), // Vienna
    Position(28.9784, 41.0082), // Istanbul
  ];

  /// Interpolate extra points between waypoints for a smoother gradient
  List<Position> _interpolate(List<Position> points, int stepsPerSegment) {
    final result = <Position>[];
    for (int i = 0; i < points.length - 1; i++) {
      final from = points[i];
      final to = points[i + 1];
      for (int s = 0; s < stepsPerSegment; s++) {
        final t = s / stepsPerSegment;
        result.add(Position(
          from.lng + (to.lng - from.lng) * t,
          from.lat + (to.lat - from.lat) * t,
        ));
      }
    }
    result.add(points.last);
    return result;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Smooth Gradient Line')),
      body: MapMetricsView(
        options: MapOptions(
          initCenter: Position(12.0, 47.0),
          initZoom: 4,
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
        ),
        onMapCreated: (MapController controller) {
          mapController = controller;
        },
        layers: _buildSmoothGradient(),
      ),
    );
  }

  List<Layer> _buildSmoothGradient() {
    final smoothPoints = _interpolate(waypoints, 20);
    final layers = <Layer>[];

    for (int i = 0; i < smoothPoints.length - 1; i++) {
      final t = i / (smoothPoints.length - 1);
      final color = Color.lerp(Colors.green, Colors.red, t)!;

      layers.add(
        PolylineLayer(
          polylines: [
            LineString(
              coordinates: [smoothPoints[i], smoothPoints[i + 1]],
            ),
          ],
          color: color,
          width: 5,
        ),
      );
    }

    return layers;
  }
}

Elevation-Based Gradient

Color the line based on simulated elevation data:

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

class ElevationGradientScreen extends StatefulWidget {
  @override
  _ElevationGradientScreenState createState() =>
      _ElevationGradientScreenState();
}

class _ElevationGradientScreenState extends State<ElevationGradientScreen> {
  MapController? mapController;

  // Points with simulated elevation data (meters) — lng, lat, elevation
  final List<Map<String, dynamic>> routeWithElevation = [
    {'lng': 2.2945, 'lat': 48.8584, 'elevation': 30},   // Eiffel Tower
    {'lng': 2.3100, 'lat': 48.8620, 'elevation': 45},
    {'lng': 2.3200, 'lat': 48.8650, 'elevation': 80},
    {'lng': 2.3300, 'lat': 48.8700, 'elevation': 60},
    {'lng': 2.3350, 'lat': 48.8750, 'elevation': 100},
    {'lng': 2.3400, 'lat': 48.8800, 'elevation': 130},  // Montmartre hill
    {'lng': 2.3431, 'lat': 48.8867, 'elevation': 130},  // Sacre-Coeur
  ];

  /// Map elevation to a color (green = low, yellow = medium, red = high)
  Color _elevationColor(int elevation) {
    final minElev = 30.0;
    final maxElev = 130.0;
    final t = ((elevation - minElev) / (maxElev - minElev)).clamp(0.0, 1.0);

    if (t < 0.5) {
      return Color.lerp(Colors.green, Colors.yellow, t * 2)!;
    } else {
      return Color.lerp(Colors.yellow, Colors.red, (t - 0.5) * 2)!;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Elevation Gradient')),
      body: Stack(
        children: [
          MapMetricsView(
            options: MapOptions(
              initCenter: Position(2.3200, 48.8700),
              initZoom: 14,
              initStyle:
                  'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
            ),
            onMapCreated: (MapController controller) {
              mapController = controller;
            },
            layers: _buildElevationLayers(),
          ),
          // Legend
          Positioned(
            bottom: 16,
            left: 16,
            child: Container(
              padding: EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(8),
                boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text('Elevation',
                      style: TextStyle(fontWeight: FontWeight.bold)),
                  SizedBox(height: 4),
                  _legendRow(Colors.green, 'Low (30m)'),
                  _legendRow(Colors.yellow, 'Medium (80m)'),
                  _legendRow(Colors.red, 'High (130m)'),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _legendRow(Color color, String label) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 2),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(width: 20, height: 4, color: color),
          SizedBox(width: 6),
          Text(label, style: TextStyle(fontSize: 12)),
        ],
      ),
    );
  }

  List<Layer> _buildElevationLayers() {
    final layers = <Layer>[];

    for (int i = 0; i < routeWithElevation.length - 1; i++) {
      final from = routeWithElevation[i];
      final to = routeWithElevation[i + 1];
      final avgElevation = ((from['elevation'] + to['elevation']) / 2).round();

      layers.add(
        PolylineLayer(
          polylines: [
            LineString(
              coordinates: [
                Position(from['lng'], from['lat']),
                Position(to['lng'], to['lat']),
              ],
            ),
          ],
          color: _elevationColor(avgElevation),
          width: 6,
        ),
      );
    }

    return layers;
  }
}

Gradient Techniques Comparison

TechniqueSegmentsSmoothnessPerformance
Waypoint segmentsFew (4-10)Visible stepsBest
Interpolated (20/seg)Many (80-200)SmoothGood
Interpolated (50/seg)Very many (200+)Very smoothModerate

Each segment above is its own PolylineLayer, and each Layer in layers: becomes a separate native GeoJSON source + style layer under the hood — hundreds of segments means hundreds of layers, which is why heavier interpolation costs more than it would with a single native gradient line. If you need thousands of smoothly-colored segments, consider driving the line via StyleController.addSource/addLayer with a raw line-gradient paint expression on a single LineStyleLayer instead (see the StyleController examples in the SDK's example app), which pushes the gradient calculation to the native renderer.

Next Steps


Tip: Use Color.lerp() to blend between any two colors. For multi-stop gradients (e.g., green -> yellow -> red), split the t value into ranges and lerp between adjacent colors.