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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Gradient Line Using Multiple Segments
Since Flutter map polylines use a single color, we simulate a gradient by breaking the route into short segments with progressively changing colors:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class GradientLineScreen extends StatefulWidget {
@override
_GradientLineScreenState createState() => _GradientLineScreenState();
}
class _GradientLineScreenState extends State<GradientLineScreen> {
MapMetricsController? mapController;
// Route through European capitals
final List<LatLng> routePoints = [
LatLng(40.4168, -3.7038), // Madrid
LatLng(48.8566, 2.3522), // Paris
LatLng(52.52, 13.405), // Berlin
LatLng(48.2082, 16.3738), // Vienna
LatLng(41.0082, 28.9784), // Istanbul
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Gradient Line')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(47.0, 12.0),
zoom: 4.0,
),
polylines: _buildGradientPolylines(),
),
);
}
/// Build a set of polyline segments that form a gradient
Set<Polyline> _buildGradientPolylines() {
final polylines = <Polyline>{};
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)!;
polylines.add(
Polyline(
polylineId: PolylineId('gradient_$i'),
points: [routePoints[i], routePoints[i + 1]],
color: color,
width: 5,
),
);
}
return polylines;
}
}Smooth Gradient with Interpolated Points
For a smoother gradient, interpolate extra points between waypoints:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class SmoothGradientLineScreen extends StatefulWidget {
@override
_SmoothGradientLineScreenState createState() =>
_SmoothGradientLineScreenState();
}
class _SmoothGradientLineScreenState extends State<SmoothGradientLineScreen> {
MapMetricsController? mapController;
final List<LatLng> waypoints = [
LatLng(40.4168, -3.7038), // Madrid
LatLng(48.8566, 2.3522), // Paris
LatLng(52.52, 13.405), // Berlin
LatLng(48.2082, 16.3738), // Vienna
LatLng(41.0082, 28.9784), // Istanbul
];
/// Interpolate extra points between waypoints for a smoother gradient
List<LatLng> _interpolate(List<LatLng> points, int stepsPerSegment) {
final result = <LatLng>[];
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(LatLng(
from.latitude + (to.latitude - from.latitude) * t,
from.longitude + (to.longitude - from.longitude) * t,
));
}
}
result.add(points.last);
return result;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Smooth Gradient Line')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(47.0, 12.0),
zoom: 4.0,
),
polylines: _buildSmoothGradient(),
),
);
}
Set<Polyline> _buildSmoothGradient() {
final smoothPoints = _interpolate(waypoints, 20);
final polylines = <Polyline>{};
for (int i = 0; i < smoothPoints.length - 1; i++) {
final t = i / (smoothPoints.length - 1);
final color = Color.lerp(Colors.green, Colors.red, t)!;
polylines.add(
Polyline(
polylineId: PolylineId('smooth_$i'),
points: [smoothPoints[i], smoothPoints[i + 1]],
color: color,
width: 5,
),
);
}
return polylines;
}
}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> {
MapMetricsController? mapController;
// Points with simulated elevation data (meters)
final List<Map<String, dynamic>> routeWithElevation = [
{'lat': 48.8584, 'lng': 2.2945, 'elevation': 30}, // Eiffel Tower
{'lat': 48.8620, 'lng': 2.3100, 'elevation': 45},
{'lat': 48.8650, 'lng': 2.3200, 'elevation': 80},
{'lat': 48.8700, 'lng': 2.3300, 'elevation': 60},
{'lat': 48.8750, 'lng': 2.3350, 'elevation': 100},
{'lat': 48.8800, 'lng': 2.3400, 'elevation': 130}, // Montmartre hill
{'lat': 48.8867, 'lng': 2.3431, '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: [
MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8700, 2.3200),
zoom: 14.0,
),
polylines: _buildElevationPolylines(),
),
// 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)),
],
),
);
}
Set<Polyline> _buildElevationPolylines() {
final polylines = <Polyline>{};
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();
polylines.add(
Polyline(
polylineId: PolylineId('elev_$i'),
points: [
LatLng(from['lat'], from['lng']),
LatLng(to['lat'], to['lng']),
],
color: _elevationColor(avgElevation),
width: 6,
),
);
}
return polylines;
}
}Gradient Techniques Comparison
| Technique | Segments | Smoothness | Performance |
|---|---|---|---|
| Waypoint segments | Few (4-10) | Visible steps | Best |
| Interpolated (20/seg) | Many (80-200) | Smooth | Good |
| Interpolated (50/seg) | Very many (200+) | Very smooth | Moderate |
Next Steps
- Add a GeoJSON Line — Simple line from GeoJSON
- Add a Polyline — Basic polyline drawing
- Animate a Line — Progressively draw a line
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.