Simple User Interface
Introduction
This Example shows how to display simple mapview and perform basic map actions:
- Display MapView Widget
- User Interface on MapView
- Customize MapView Size
- MapView UI Settings
- MapView GestureDetector
![]() | ![]() |
Android snapshot | iOS snapshot |
For all code examples, refer to Flutter Maps SDK Code Examples
MapUiPage view source
1import 'dart:math';
2
3import 'package:flutter/material.dart';
4import 'package:collection/collection.dart';
5import 'package:nb_maps_flutter/nb_maps_flutter.dart';
6
7import 'main.dart';
8import 'page.dart';
9
10final LatLngBounds sydneyBounds = LatLngBounds(
11 southwest: const LatLng(-34.022631, 150.620685),
12 northeast: const LatLng(-33.571835, 151.325952),
13);
14
15class MapUiPage extends ExamplePage {
16 MapUiPage() : super(const Icon(Icons.map), 'User interface');
17
18
19 Widget build(BuildContext context) {
20 return const MapUiBody();
21 }
22}
23
24class MapUiBody extends StatefulWidget {
25 const MapUiBody();
26
27
28 State<StatefulWidget> createState() => MapUiBodyState();
29}
30
31class MapUiBodyState extends State<MapUiBody> {
32 MapUiBodyState();
33
34 static final CameraPosition _kInitialPosition = const CameraPosition(
35 target: LatLng(-33.852, 151.211),
36 zoom: 11.0,
37 );
38
39 NextbillionMapController? mapController;
40 CameraPosition _position = _kInitialPosition;
41 bool _isMoving = false;
42 bool _compassEnabled = true;
43 bool _mapExpanded = true;
44 CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded;
45 MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded;
46 int _styleStringIndex = 0;
47 // Style string can a reference to a local or remote resources.
48 // On Android the raw JSON can also be passed via a styleString, on iOS this is not supported.
49 List<String> _styleStrings = [
50 "https://api.nextbillion.io/maps/streets/style.json",
51 "https://api.nextbillion.io/maps/streets/style.json",
52 "https://api.nextbillion.io/maps/streets/style.json"
53 ];
54 List<String> _styleStringLabels = [
55 "NBMAP_STREETS",
56 "SATELLITE",
57 "LOCAL_ASSET"
58 ];
59 bool _rotateGesturesEnabled = true;
60 bool _scrollGesturesEnabled = true;
61 bool? _doubleClickToZoomEnabled;
62 bool _tiltGesturesEnabled = true;
63 bool _zoomGesturesEnabled = true;
64 bool _myLocationEnabled = true;
65 bool _telemetryEnabled = true;
66 MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.None;
67 List<Object>? _featureQueryFilter;
68 Fill? _selectedFill;
69
70
71 void initState() {
72 super.initState();
73 }
74
75 void _onMapChanged() {
76 setState(() {
77 _extractMapInfo();
78 });
79 }
80
81 void _extractMapInfo() {
82 final position = mapController!.cameraPosition;
83 if (position != null) _position = position;
84 _isMoving = mapController!.isCameraMoving;
85 }
86
87
88 void dispose() {
89 mapController?.removeListener(_onMapChanged);
90 super.dispose();
91 }
92
93 Widget _myLocationTrackingModeCycler() {
94 final MyLocationTrackingMode nextType = MyLocationTrackingMode.values[
95 (_myLocationTrackingMode.index + 1) %
96 MyLocationTrackingMode.values.length];
97 return TextButton(
98 child: Text('change to $nextType'),
99 onPressed: () {
100 setState(() {
101 _myLocationTrackingMode = nextType;
102 });
103 },
104 );
105 }
106
107 Widget _queryFilterToggler() {
108 return TextButton(
109 child: Text(
110 'filter zoo on click ${_featureQueryFilter == null ? 'disabled' : 'enabled'}'),
111 onPressed: () {
112 setState(() {
113 if (_featureQueryFilter == null) {
114 _featureQueryFilter = [
115 "==",
116 ["get", "type"],
117 "zoo"
118 ];
119 } else {
120 _featureQueryFilter = null;
121 }
122 });
123 },
124 );
125 }
126
127 Widget _mapSizeToggler() {
128 return TextButton(
129 child: Text('${_mapExpanded ? 'shrink' : 'expand'} map'),
130 onPressed: () {
131 setState(() {
132 _mapExpanded = !_mapExpanded;
133 });
134 },
135 );
136 }
137
138 Widget _compassToggler() {
139 return TextButton(
140 child: Text('${_compassEnabled ? 'disable' : 'enable'} compasss'),
141 onPressed: () {
142 setState(() {
143 _compassEnabled = !_compassEnabled;
144 });
145 },
146 );
147 }
148
149 Widget _latLngBoundsToggler() {
150 return TextButton(
151 child: Text(
152 _cameraTargetBounds.bounds == null
153 ? 'bound camera target'
154 : 'release camera target',
155 ),
156 onPressed: () {
157 setState(() {
158 _cameraTargetBounds = _cameraTargetBounds.bounds == null
159 ? CameraTargetBounds(sydneyBounds)
160 : CameraTargetBounds.unbounded;
161 });
162 },
163 );
164 }
165
166 Widget _zoomBoundsToggler() {
167 return TextButton(
168 child: Text(_minMaxZoomPreference.minZoom == null
169 ? 'bound zoom'
170 : 'release zoom'),
171 onPressed: () {
172 setState(() {
173 _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null
174 ? const MinMaxZoomPreference(12.0, 16.0)
175 : MinMaxZoomPreference.unbounded;
176 });
177 },
178 );
179 }
180
181 Widget _setStyleToSatellite() {
182 return TextButton(
183 child: Text(
184 'change map style to ${_styleStringLabels[(_styleStringIndex + 1) % _styleStringLabels.length]}'),
185 onPressed: () {
186 setState(() {
187 _styleStringIndex = (_styleStringIndex + 1) % _styleStrings.length;
188 });
189 },
190 );
191 }
192
193 Widget _rotateToggler() {
194 return TextButton(
195 child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
196 onPressed: () {
197 setState(() {
198 _rotateGesturesEnabled = !_rotateGesturesEnabled;
199 });
200 },
201 );
202 }
203
204 Widget _scrollToggler() {
205 return TextButton(
206 child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
207 onPressed: () {
208 setState(() {
209 _scrollGesturesEnabled = !_scrollGesturesEnabled;
210 });
211 },
212 );
213 }
214
215 Widget _doubleClickToZoomToggler() {
216 final stateInfo = _doubleClickToZoomEnabled == null
217 ? "disable"
218 : _doubleClickToZoomEnabled!
219 ? 'unset'
220 : 'enable';
221 return TextButton(
222 child: Text('$stateInfo double click to zoom'),
223 onPressed: () {
224 setState(() {
225 if (_doubleClickToZoomEnabled == null) {
226 _doubleClickToZoomEnabled = false;
227 } else if (!_doubleClickToZoomEnabled!) {
228 _doubleClickToZoomEnabled = true;
229 } else {
230 _doubleClickToZoomEnabled = null;
231 }
232 });
233 },
234 );
235 }
236
237 Widget _tiltToggler() {
238 return TextButton(
239 child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
240 onPressed: () {
241 setState(() {
242 _tiltGesturesEnabled = !_tiltGesturesEnabled;
243 });
244 },
245 );
246 }
247
248 Widget _zoomToggler() {
249 return TextButton(
250 child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
251 onPressed: () {
252 setState(() {
253 _zoomGesturesEnabled = !_zoomGesturesEnabled;
254 });
255 },
256 );
257 }
258
259 Widget _myLocationToggler() {
260 return TextButton(
261 child: Text('${_myLocationEnabled ? 'disable' : 'enable'} my location'),
262 onPressed: () {
263 setState(() {
264 _myLocationEnabled = !_myLocationEnabled;
265 });
266 },
267 );
268 }
269
270 Widget _telemetryToggler() {
271 return TextButton(
272 child: Text('${_telemetryEnabled ? 'disable' : 'enable'} telemetry'),
273 onPressed: () {
274 setState(() {
275 _telemetryEnabled = !_telemetryEnabled;
276 });
277 mapController?.setTelemetryEnabled(_telemetryEnabled);
278 },
279 );
280 }
281
282 Widget _visibleRegionGetter() {
283 return TextButton(
284 child: Text('get currently visible region'),
285 onPressed: () async {
286 var result = await mapController!.getVisibleRegion();
287 ScaffoldMessenger.of(context).showSnackBar(SnackBar(
288 content: Text(
289 "SW: ${result.southwest.toString()} NE: ${result.northeast.toString()}"),
290 ));
291 },
292 );
293 }
294
295 _clearFill() {
296 if (_selectedFill != null) {
297 mapController!.removeFill(_selectedFill!);
298 setState(() {
299 _selectedFill = null;
300 });
301 }
302 }
303
304 _drawFill(List<dynamic> features) async {
305 Map<String, dynamic>? feature =
306 features.firstWhereOrNull((f) => f['geometry']['type'] == 'Polygon');
307
308 if (feature != null) {
309 List<List<LatLng>> geometry = feature['geometry']['coordinates']
310 .map(
311 (ll) => ll.map((l) => LatLng(l[1], l[0])).toList().cast<LatLng>())
312 .toList()
313 .cast<List<LatLng>>();
314 Fill fill = await mapController!.addFill(FillOptions(
315 geometry: geometry,
316 fillColor: "#FF0000",
317 fillOutlineColor: "#FF0000",
318 fillOpacity: 0.6,
319 ));
320 setState(() {
321 _selectedFill = fill;
322 });
323 }
324 }
325
326
327 Widget build(BuildContext context) {
328 final NBMap nbMap = NBMap(
329 onMapCreated: onMapCreated,
330 initialCameraPosition: _kInitialPosition,
331 trackCameraPosition: true,
332 compassEnabled: _compassEnabled,
333 cameraTargetBounds: _cameraTargetBounds,
334 minMaxZoomPreference: _minMaxZoomPreference,
335 styleString: _styleStrings[_styleStringIndex],
336 rotateGesturesEnabled: _rotateGesturesEnabled,
337 scrollGesturesEnabled: _scrollGesturesEnabled,
338 tiltGesturesEnabled: _tiltGesturesEnabled,
339 zoomGesturesEnabled: _zoomGesturesEnabled,
340 doubleClickZoomEnabled: _doubleClickToZoomEnabled,
341 myLocationEnabled: _myLocationEnabled,
342 myLocationTrackingMode: _myLocationTrackingMode,
343 myLocationRenderMode: MyLocationRenderMode.GPS,
344 onMapClick: (point, latLng) async {
345 print(
346 "Map click: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}");
347 print("Filter $_featureQueryFilter");
348 List features = await mapController!
349 .queryRenderedFeatures(point, ["landuse"], _featureQueryFilter);
350 print('# features: ${features.length}');
351 _clearFill();
352 if (features.isEmpty && _featureQueryFilter != null) {
353 ScaffoldMessenger.of(context).showSnackBar(SnackBar(
354 content: Text('QueryRenderedFeatures: No features found!')));
355 } else if (features.isNotEmpty) {
356 _drawFill(features);
357 }
358 },
359 onMapLongClick: (point, latLng) async {
360 print(
361 "Map long press: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}");
362 Point convertedPoint = await mapController!.toScreenLocation(latLng);
363 LatLng convertedLatLng = await mapController!.toLatLng(point);
364 print(
365 "Map long press converted: ${convertedPoint.x},${convertedPoint.y} ${convertedLatLng.latitude}/${convertedLatLng.longitude}");
366 double metersPerPixel =
367 await mapController!.getMetersPerPixelAtLatitude(latLng.latitude);
368
369 print(
370 "Map long press The distance measured in meters at latitude ${latLng.latitude} is $metersPerPixel m");
371
372 List features =
373 await mapController!.queryRenderedFeatures(point, [], null);
374 if (features.length > 0) {
375 print(features[0]);
376 }
377 },
378 onCameraTrackingDismissed: () {
379 this.setState(() {
380 _myLocationTrackingMode = MyLocationTrackingMode.None;
381 });
382 },
383 onUserLocationUpdated: (location) {
384 print(
385 "new location: ${location.position}, alt.: ${location.altitude}, bearing: ${location.bearing}, speed: ${location.speed}, horiz. accuracy: ${location.horizontalAccuracy}, vert. accuracy: ${location.verticalAccuracy}");
386 },
387 );
388
389 final List<Widget> listViewChildren = <Widget>[];
390
391 if (mapController != null) {
392 listViewChildren.addAll(
393 <Widget>[
394 Text('camera bearing: ${_position.bearing}'),
395 Text('camera target: ${_position.target.latitude.toStringAsFixed(4)},'
396 '${_position.target.longitude.toStringAsFixed(4)}'),
397 Text('camera zoom: ${_position.zoom}'),
398 Text('camera tilt: ${_position.tilt}'),
399 Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
400 _mapSizeToggler(),
401 _queryFilterToggler(),
402 _compassToggler(),
403 _myLocationTrackingModeCycler(),
404 _latLngBoundsToggler(),
405 _setStyleToSatellite(),
406 _zoomBoundsToggler(),
407 _rotateToggler(),
408 _scrollToggler(),
409 _doubleClickToZoomToggler(),
410 _tiltToggler(),
411 _zoomToggler(),
412 _myLocationToggler(),
413 _telemetryToggler(),
414 _visibleRegionGetter(),
415 ],
416 );
417 }
418 return Column(
419 mainAxisSize: MainAxisSize.min,
420 children: [
421 Center(
422 child: SizedBox(
423 width: _mapExpanded ? null : 300.0,
424 height: 200.0,
425 child: nbMap,
426 ),
427 ),
428 Expanded(
429 child: ListView(
430 children: listViewChildren,
431 ),
432 )
433 ],
434 );
435 }
436
437 void onMapCreated(NextbillionMapController controller) {
438 mapController = controller;
439 mapController!.addListener(_onMapChanged);
440 _extractMapInfo();
441
442 mapController!.getTelemetryEnabled().then((isEnabled) => setState(() {
443 _telemetryEnabled = isEnabled;
444 }));
445 }
446}
Code summary
The above code snippet defines a MapUiBody widget that displays a map using the NBMap widget from the nb_maps_flutter package. The widget allows users to interact with the map and control various aspects of its user interface.
-
Display MapView Widget:
- The NBMap widget is used to display the map on the screen.
- The onMapCreated callback is used to get access to the NextbillionMapController, which allows interaction with the map.
- The initialCameraPosition property sets the initial position of the camera when the map is first displayed.
-
User Interface on MapView: The MapUiBody widget provides several buttons and toggles that allow users to interact with the map and customize its behavior:
- Camera Information: Displays the current camera bearing, target latitude and longitude, zoom level, and tilt. Also shows whether the camera is moving or idle.
- Map Size Toggler: Toggles between expanding and shrinking the map view.
- Query Filter Toggler: Toggles the filter for querying features on the map.
- Compass Toggler: Toggles the compass display on the map.
- My Location Tracking Mode Cycler: Cycles through different tracking modes for user location.
- Camera Target Bounds Toggler: Toggles between bounding and releasing the camera target within specific bounds.
- Set Style To Satellite: Cycles through different map styles (NBMAP_STREETS, SATELLITE, LOCAL_ASSET).
- Zoom Bounds Toggler: Toggles between bounding and releasing the zoom level within specific bounds.
- Rotate Toggler: Toggles the rotate gesture on the map.
- Scroll Toggler: Toggles the scroll gesture on the map.
- Double Click To Zoom Toggler: Toggles the double click to zoom gesture on the map.
- Tilt Toggler: Toggles the tilt gesture on the map.
- Zoom Toggler: Toggles the zoom gesture on the map.
- My Location Toggler: Toggles the display of the user's location on the map.
- Telemetry Toggler: Toggles the telemetry tracking on the map.
- Visible Region Getter: Retrieves the currently visible region on the map and displays it in a snackbar.
To use this widget in your Flutter app, make sure you have installed the nb_maps_flutter package and added the necessary dependencies to your pubspec.yaml file.
Overall, this code provides a comprehensive example of how to use the NBMap widget and interact with the map's user interface in a Flutter app using the NextbillionMap SDK.