MapView Markers
Introduction
This example shows how to add markers in bulk
- Add markers in bulk to MapView
- Long Click MapView to Add a marker with clicked point
![]() | ![]() |
Android snapshot | iOS snapshot |
For all code examples, refer to Flutter Maps SDK Code Examples
CustomMarkerPage view source
1import 'dart:io';
2import 'dart:math';
3
4import 'package:flutter/foundation.dart';
5import 'package:flutter/material.dart';
6import 'package:flutter/widgets.dart'; // ignore: unnecessary_import
7import 'package:nb_maps_flutter/nb_maps_flutter.dart';
8
9import 'page.dart';
10
11const randomMarkerNum = 10;
12
13class CustomMarkerPage extends ExamplePage {
14 CustomMarkerPage() : super(const Icon(Icons.place), 'Custom marker');
15
16
17 Widget build(BuildContext context) {
18 return CustomMarker();
19 }
20}
21
22class CustomMarker extends StatefulWidget {
23 const CustomMarker();
24
25
26 State createState() => CustomMarkerState();
27}
28
29class CustomMarkerState extends State<CustomMarker> {
30 final Random _rnd = new Random();
31
32 late NextbillionMapController _mapController;
33 List<Marker> _markers = [];
34 List<_MarkerState> _markerStates = [];
35
36 void _addMarkerStates(_MarkerState markerState) {
37 _markerStates.add(markerState);
38 }
39
40 void _onMapCreated(NextbillionMapController controller) {
41 _mapController = controller;
42 controller.addListener(() {
43 if (controller.isCameraMoving) {
44 _updateMarkerPosition();
45 }
46 });
47 }
48
49 void _onStyleLoadedCallback() {
50 print('onStyleLoadedCallback');
51 }
52
53 void _onMapLongClickCallback(Point<double> point, LatLng coordinates) {
54 _addMarker(point, coordinates);
55 }
56
57 void _onCameraIdleCallback() {
58 _updateMarkerPosition();
59 }
60
61 void _updateMarkerPosition() {
62 final coordinates = <LatLng>[];
63
64 for (final markerState in _markerStates) {
65 coordinates.add(markerState.getCoordinate());
66 }
67
68 _mapController.toScreenLocationBatch(coordinates).then((points) {
69 _markerStates.asMap().forEach((i, value) {
70 _markerStates[i].updatePosition(points[i]);
71 });
72 });
73 }
74
75 void _addMarker(Point<double> point, LatLng coordinates) {
76 setState(() {
77 _markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point,
78 _addMarkerStates));
79 });
80 }
81
82
83 Widget build(BuildContext context) {
84 return new Container(
85 child: Stack(children: [
86 NBMap(
87 trackCameraPosition: true,
88 onMapCreated: _onMapCreated,
89 onMapLongClick: _onMapLongClickCallback,
90 onCameraIdle: _onCameraIdleCallback,
91 onStyleLoadedCallback: _onStyleLoadedCallback,
92 initialCameraPosition:
93 const CameraPosition(target: LatLng(35.0, 135.0), zoom: 5),
94 ),
95 IgnorePointer(
96 ignoring: true,
97 child: Stack(
98 children: _markers,
99 )),
100 FloatingActionButton(
101 onPressed: () {
102 // Generate random markers
103 var param = <LatLng>[];
104 for (var i = 0; i < randomMarkerNum; i++) {
105 final lat = _rnd.nextDouble() * 20 + 30;
106 final lng = _rnd.nextDouble() * 20 + 125;
107 param.add(LatLng(lat, lng));
108 }
109
110 _mapController.toScreenLocationBatch(param).then((value) {
111 for (var i = 0; i < randomMarkerNum; i++) {
112 var point =
113 Point<double>(value[i].x as double, value[i].y as double);
114 _addMarker(point, param[i]);
115 }
116 });
117 },
118 child: Icon(Icons.add),
119 ),
120 ]),
121 );
122 }
123
124 // ignore: unused_element
125 void _measurePerformance() {
126 final trial = 10;
127 final batches = [500, 1000, 1500, 2000, 2500, 3000];
128 var results = Map<int, List<double>>();
129 for (final batch in batches) {
130 results[batch] = [0.0, 0.0];
131 }
132
133 _mapController.toScreenLocation(LatLng(0, 0));
134 Stopwatch sw = Stopwatch();
135
136 for (final batch in batches) {
137 //
138 // primitive
139 //
140 for (var i = 0; i < trial; i++) {
141 sw.start();
142 var list = <Future<Point<num>>>[];
143 for (var j = 0; j < batch; j++) {
144 var p = _mapController
145 .toScreenLocation(LatLng(j.toDouble() % 80, j.toDouble() % 300));
146 list.add(p);
147 }
148 Future.wait(list);
149 sw.stop();
150 results[batch]![0] += sw.elapsedMilliseconds;
151 sw.reset();
152 }
153
154 //
155 // batch
156 //
157 for (var i = 0; i < trial; i++) {
158 sw.start();
159 var param = <LatLng>[];
160 for (var j = 0; j < batch; j++) {
161 param.add(LatLng(j.toDouble() % 80, j.toDouble() % 300));
162 }
163 Future.wait([_mapController.toScreenLocationBatch(param)]);
164 sw.stop();
165 results[batch]![1] += sw.elapsedMilliseconds;
166 sw.reset();
167 }
168
169 print(
170 'batch=$batch,primitive=${results[batch]![0] / trial}ms, batch=${results[batch]![1] / trial}ms');
171 }
172 }
173}
174
175class Marker extends StatefulWidget {
176 final Point _initialPosition;
177 final LatLng _coordinate;
178 final void Function(_MarkerState) _addMarkerState;
179
180 Marker(
181 String key, this._coordinate, this._initialPosition, this._addMarkerState)
182 : super(key: Key(key));
183
184
185 State<StatefulWidget> createState() {
186 final state = _MarkerState(_initialPosition);
187 _addMarkerState(state);
188 return state;
189 }
190}
191
192class _MarkerState extends State with TickerProviderStateMixin {
193 final _iconSize = 20.0;
194
195 Point _position;
196
197 _MarkerState(this._position);
198
199
200 void initState() {
201 super.initState();
202 }
203
204
205 void dispose() {
206 super.dispose();
207 }
208
209
210 Widget build(BuildContext context) {
211 var ratio = 1.0;
212
213 // web does not support Platform._operatingSystem
214 if (!kIsWeb) {
215 // iOS returns logical pixel while Android returns screen pixel
216 ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio;
217 }
218
219 return Positioned(
220 left: _position.x / ratio - _iconSize / 2,
221 top: _position.y / ratio - _iconSize / 2,
222 child: Image.asset('assets/symbols/2.0x/custom-icon.png',
223 height: _iconSize));
224 }
225
226 void updatePosition(Point<num> point) {
227 setState(() {
228 _position = point;
229 });
230 }
231
232 LatLng getCoordinate() {
233 return (widget as Marker)._coordinate;
234 }
235}
Code summary
The above code snippet demonstrates how to implement a custom marker feature in a Flutter application using the nb_maps_flutter package. The custom markers are added to the map when:
- the user performs a long-press gesture
- User click on the floating button on the top left corner And the marker positions are updated when the map is moved.
CustomMarker Class: is a StatefulWidget class that represents the custom marker widget. It uses the NextbillionMapController to interact with the map and manages a list of custom _MarkerState instances to keep track of the markers on the map.
CustomMarkerState Class: is the state class for the CustomMarker widget. It handles map-related events and manages the markers on the map. Notable methods include:
- _onMapCreated: Handles the initialization of the map controller and sets up a listener for camera movements.
- _onStyleLoadedCallback: A callback method that is called when the map style is loaded.
- _onMapLongClickCallback: Handles the event when the user long-presses on the map to add a new custom marker.
- _onCameraIdleCallback: Handles the event when the camera stops moving and triggers the update of marker positions.
- _addMarker: Adds a new custom marker to the map and updates the list of markers.
build Method: returns a Container widget containing a Stack with multiple child widgets:
- NBMap: The actual map widget, provided by the nb_maps_flutter package. It handles user interactions and displays the custom markers on the map.
- IgnorePointer: A widget that ignores touch events, ensuring that the custom markers do not interfere with user interactions with the map.
- FloatingActionButton: A button that, when pressed, generates and adds random markers to the map.
_MarkerState Class: This class represents the state of a custom marker. It extends State and is responsible for rendering the custom marker image on the map. Notable methods include:
- build: Builds the marker widget based on the marker's position and icon image.
- updatePosition: Updates the marker's position when the map is moved.
- getCoordinate: Retrieves the coordinate of the marker on the map.