Skip to content

Commit f5d1894

Browse files
authored
Add custom colorized vector icons examples for Flutter (#1072)
* Add custom colorized vector icons examples for Flutter MAPSFLT-411 Port the custom colorized vector icons example from GL JS to Flutter. Demonstrates parameterized SVG icons with runtime color customization using the image expression with color parameters. * Add interactive icon sizing to Custom Vector Icons example Tap flags to toggle their size between 1x and 2x, matching the iOS example functionality. Renamed from "Custom Colorized Vector Icons" to reflect the added interaction capabilities.
1 parent 0a11316 commit f5d1894

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import 'dart:convert';
2+
import 'package:flutter/material.dart';
3+
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
4+
import 'example.dart';
5+
6+
/// Example demonstrating custom vector icons with dynamic styling and interaction.
7+
/// This example shows how to:
8+
/// - Dynamically colorize vector icons based on feature properties using the image expression
9+
/// - Interactively change icon size by tapping on icons
10+
///
11+
/// Vector icons are parameterized SVG images that can be styled at runtime. In this example,
12+
/// three flag icons are colored red, yellow, and purple using the 'flagColor' property.
13+
/// Tap any flag to toggle its size between 1x and 2x.
14+
///
15+
/// For this example to work, the SVGs must live inside the map style. The SVG file was uploaded
16+
/// to Mapbox Studio with the name `flag`, making it available for customization at runtime.
17+
/// You can add vector icons to your own style in Mapbox Studio.
18+
class CustomVectorIconsExample extends StatefulWidget
19+
implements Example {
20+
@override
21+
final Widget leading = const Icon(Icons.flag);
22+
@override
23+
final String title = 'Custom Vector Icons';
24+
@override
25+
final String subtitle =
26+
'Colorize and interact with vector icons using parameterized SVGs';
27+
28+
@override
29+
State<StatefulWidget> createState() =>
30+
_CustomVectorIconsExampleState();
31+
}
32+
33+
class _CustomVectorIconsExampleState
34+
extends State<CustomVectorIconsExample> {
35+
MapboxMap? mapboxMap;
36+
String? selectedFlagId;
37+
38+
@override
39+
Widget build(BuildContext context) {
40+
return MapWidget(
41+
cameraOptions: CameraOptions(
42+
center: Point(coordinates: Position(24.6881, 60.185755)),
43+
zoom: 16.0,
44+
),
45+
styleUri: 'mapbox://styles/mapbox-map-design/cm4r19bcm00ao01qvhp3jc2gi',
46+
key: ValueKey<String>('mapWidget'),
47+
onMapCreated: _onMapCreated,
48+
onStyleLoadedListener: _onStyleLoaded,
49+
);
50+
}
51+
52+
_onMapCreated(MapboxMap mapboxMap) async {
53+
this.mapboxMap = mapboxMap;
54+
55+
// Add tap interaction for the symbol layer
56+
var tapInteraction = TapInteraction(
57+
FeaturesetDescriptor(layerId: "points"), (feature, point) {
58+
final id = feature.id?.id;
59+
if (id == null) return;
60+
61+
setState(() {
62+
// Toggle selection: if tapping the same feature, deselect; otherwise select new one
63+
selectedFlagId = (selectedFlagId == id) ? null : id;
64+
});
65+
66+
// Update icon size expression based on selection
67+
mapboxMap.style.setStyleLayerProperty(
68+
'points',
69+
'icon-size',
70+
[
71+
'case',
72+
['==', ['id'], selectedFlagId ?? ''],
73+
2.0,
74+
1.0
75+
],
76+
);
77+
});
78+
mapboxMap.addInteraction(tapInteraction,
79+
interactionID: "tap_interaction_flags");
80+
}
81+
82+
_onStyleLoaded(StyleLoadedEventData data) async {
83+
await _addFlagSymbols();
84+
}
85+
86+
/// Creates GeoJSON features with flag locations and colors.
87+
List<Map<String, dynamic>> _createFlagFeatures() {
88+
return [
89+
_createFlagFeature('flag-red', 24.68727, 60.185755, 'red'),
90+
_createFlagFeature('flag-yellow', 24.68827, 60.186255, 'yellow'),
91+
_createFlagFeature('flag-purple', 24.68927, 60.186055, '#800080'),
92+
];
93+
}
94+
95+
/// Creates a feature with a flag at the specified location and color.
96+
Map<String, dynamic> _createFlagFeature(
97+
String id, double longitude, double latitude, String color) {
98+
return {
99+
'type': 'Feature',
100+
'id': id, // Feature ID used for selection and icon size expression
101+
'geometry': {
102+
'type': 'Point',
103+
'coordinates': [longitude, latitude],
104+
},
105+
'properties': {
106+
'flagColor': color,
107+
},
108+
};
109+
}
110+
111+
Future<void> _addFlagSymbols() async {
112+
if (mapboxMap == null) {
113+
throw Exception("MapboxMap is not ready yet");
114+
}
115+
116+
// Create GeoJSON FeatureCollection
117+
final geojson = {
118+
'type': 'FeatureCollection',
119+
'features': _createFlagFeatures(),
120+
};
121+
122+
// Add GeoJSON source with flag locations
123+
await mapboxMap?.style.addSource(
124+
GeoJsonSource(
125+
id: 'points',
126+
data: json.encode(geojson),
127+
),
128+
);
129+
130+
// Create symbol layer with parameterized icon
131+
// The expression uses the 'image' operator with a params object
132+
// that maps the 'flag_color' parameter to the 'flagColor' property
133+
final layer = SymbolLayer(
134+
id: 'points',
135+
sourceId: 'points',
136+
iconImageExpression: [
137+
'image',
138+
'flag',
139+
{
140+
'params': {
141+
'flag_color': ['get', 'flagColor']
142+
}
143+
}
144+
],
145+
);
146+
147+
await mapboxMap?.style.addLayer(layer);
148+
}
149+
}

example/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:mapbox_maps_example/animation_example.dart';
44
import 'package:mapbox_maps_example/camera_example.dart';
55
import 'package:mapbox_maps_example/circle_annotations_example.dart';
66
import 'package:mapbox_maps_example/cluster_example.dart';
7+
import 'package:mapbox_maps_example/custom_vector_icons_example.dart';
78
import 'package:mapbox_maps_example/custom_header_example.dart';
89
import 'package:mapbox_maps_example/draggable-annotations-example.dart';
910
import 'package:mapbox_maps_example/edit_polygon_example.dart';
@@ -74,6 +75,7 @@ final List<Example> _allPages = <Example>[
7475
AnimatedRouteExample(),
7576
CustomHeaderExample(),
7677
TrafficLayerExample(),
78+
CustomVectorIconsExample(),
7779
];
7880

7981
class MapsDemo extends StatelessWidget {

0 commit comments

Comments
 (0)