In Maps, markers are a primary tool used to denote locations with precise latitude and longitude coordinates. The Syncfusion Flutter Maps widget has built-in support to add markers on the MapShapeLayer or MapTileLayer. You can use any type of custom widget as a marker, or built-in shapes such as circles, diamonds, squares, and triangles.
In this blog, I am going to explain how to add markers and animate them. If you are new to our Flutter Maps widget, please refer to the following blog posts before proceeding:
To add an animated marker at load time, we have to add an animated widget as a child of MapMarker and start the animation in the initState.
In the following example, I’ve animated the fourth index marker at load time.
class _LoadAnimatableMarkerAtLoadtime extends StatefulWidget { @override _LoadAnimatableMarkerAtLoadtimeState createState() => _LoadAnimatableMarkerAtLoadtimeState(); } class _LoadAnimatableMarkerAtLoadtimeState extends State<_LoadAnimatableMarkerAtLoadtime> with SingleTickerProviderStateMixin { late AnimationController _controller; late CurvedAnimation _animation; late Map<String, MapLatLng> _markers; int _selectedMarkerIndex = 4; @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 750)); _animation = CurvedAnimation(parent: _controller, curve: Curves.easeOutBack); _markers = <String, MapLatLng>{ 'Chad': MapLatLng(15.454166, 18.732206), 'Nigeria': MapLatLng(9.081999, 8.675277), 'DRC': MapLatLng(-4.038333, 21.758663), 'CAR': MapLatLng(6.600281, 20.480205), 'Sudan': MapLatLng(12.862807, 30.217636), 'Kenya': MapLatLng(0.0236, 37.9062), 'Zambia': MapLatLng(-10.974129, 30.861397), 'Egypt': MapLatLng(25.174109, 28.776359), 'Algeria': MapLatLng(24.276672, 7.308186), }; _controller.repeat(min: 0.15, max: 1.0, reverse: true); super.initState(); } @override void dispose() { _controller.stop(); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Load animatable marker at loadtime'), ), body: MapTileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', initialZoomLevel: 3, initialFocalLatLng: MapLatLng(2.3104, 16.5581), initialMarkersCount: _markers.length, markerBuilder: (BuildContext context, int index) { final double size = _selectedMarkerIndex == index ? 40 : 25; final MapLatLng markerData = _markers.values.elementAt(index); final Icon current = Icon(Icons.location_pin, size: size); return MapMarker( latitude: markerData.latitude, longitude: markerData.longitude, child: Transform.translate( offset: Offset(0.0, -size / 2), child: _selectedMarkerIndex == index ? ScaleTransition( alignment: Alignment.bottomCenter, scale: _animation, child: current) : current, ), ); }, ), ); } }
To add or update markers dynamically, we will need the help of MapShapeLayerController or MapTileLayerController depending on whether we use MapShapeLayer or MapTileLayer to include the marker. MapShapeLayerController and MapTileLayerController control the marker collection dynamically by adding, removing, updating, and clearing the markers.
In the following example, we will add a marker dynamically at a precise coordinate by using the insertMarker method of MapShapeLayerController. Here, we use the Icon widget as a marker. Calling the insertMarker method of the MapShapeLayerControl triggers the TileLayer’s markerBuilder callback with the respective index. Here, we have to return the MapMarker to be updated in the current index.
class AddingMarkerDynamically extends StatefulWidget { @override _AddingMarkerDynamicallyState createState() => _AddingMarkerDynamicallyState(); } class _AddingMarkerDynamicallyState extends State with SingleTickerProviderStateMixin { late MapTileLayerController _tileLayerController; @override void initState() { _tileLayerController = MapTileLayerController(); super.initState(); } @override void dispose() { _tileLayerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Add a marker dynamically'), ), body: MapTileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', initialZoomLevel: 3, initialFocalLatLng: MapLatLng(2.3104, 16.5581), controller: _tileLayerController, markerBuilder: (BuildContext context, int index) { return MapMarker( latitude: 6.6111, longitude: 20.9394, child: Transform.translate( offset: Offset(0.0, -20.0), child: Icon(Icons.location_pin, size: 40), ), ); }, ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add_location), onPressed: () => _tileLayerController.insertMarker(0), ), ); } }
Dynamic marker updates require the help of MapShapeLayerController and MapTileLayerController. The following example demonstrates updating MapMarker’s child dynamically as an animated widget to start the animation.
In parallel, invoke updateMarkers with respective marker indices to update the markers.
class AnimateGroupOfMarkersDynamically extends StatefulWidget { @override _AnimateGroupOfMarkersDynamicallyState createState() => _AnimateGroupOfMarkersDynamicallyState(); } class _AnimateGroupOfMarkersDynamicallyState extends State with TickerProviderStateMixin { late AnimationController _controller; late CurvedAnimation _animation; late MapTileLayerController _tileLayerController; late Map<String, MapLatLng> _markers; List _selectedMarkerIndices = []; @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 750), reverseDuration: const Duration(milliseconds: 750)); _animation = CurvedAnimation(parent: _controller, curve: Curves.easeOutBack); _tileLayerController = MapTileLayerController(); _markers = <String, MapLatLng>{ 'Chad': MapLatLng(15.454166, 18.732206), 'Nigeria': MapLatLng(9.081999, 8.675277), 'DRC': MapLatLng(-4.038333, 21.758663), 'CAR': MapLatLng(6.600281, 20.480205), 'Sudan': MapLatLng(12.862807, 30.217636), 'Kenya': MapLatLng(0.0236, 37.9062), 'Zambia': MapLatLng(-10.974129, 30.861397), 'Egypt': MapLatLng(25.174109, 28.776359), 'Algeria': MapLatLng(24.276672, 7.308186), }; _controller.repeat(min: 0.1, max: 1.0, reverse: true); super.initState(); } @override void dispose() { _controller.stop(); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Animate group of markers dynamically')), body: MapTileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', initialZoomLevel: 3, initialFocalLatLng: MapLatLng(2.3104, 16.5581), controller: _tileLayerController, initialMarkersCount: _markers.length, markerBuilder: (BuildContext context, int index) { final double size = _selectedMarkerIndices.contains(index) ? 40 : 25; final MapLatLng markerLatLng = _markers.values.elementAt(index); Widget current = Icon(Icons.location_pin, size: size); return MapMarker( latitude: markerLatLng.latitude, longitude: markerLatLng.longitude, child: Transform.translate( offset: Offset(0.0, -size / 2), child: _selectedMarkerIndices.contains(index) ? ScaleTransition( alignment: Alignment.bottomCenter, scale: _animation, child: current) : current, ), ); }, ), floatingActionButton: FloatingActionButton( child: Icon(Icons.animation), onPressed: () { _selectedMarkerIndices = [0, 2, 4]; _tileLayerController.updateMarkers(_selectedMarkerIndices); _controller.repeat(min: 0.1, max: 1.0, reverse: true); }, ), ); } }
Assume we have a group of markers in which we are going to animate a single marker on a tap or click gesture.
For this, we need to wrap the marker’s child inside the GestureDetector and use the GestureDetector.onTap callback to handle the tap interaction. On tapping, call updateMarkers with the marker indices to animate it.
The following code demonstrates this.
int _selectedMarkerIndex = -1; int _prevSelectedMarkerIndex = -1; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animate a single marker dynamically'), ), body: MapTileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', initialZoomLevel: 3, initialFocalLatLng: MapLatLng(2.3104, 16.5581), controller: _tileLayerController, initialMarkersCount: _markers.length, markerBuilder: (BuildContext context, int index) => _buildMarker(context, index), ), ); } MapMarker _buildMarker(BuildContext context, int index) { final double size = _selectedMarkerIndex == index ? 40 : 25; final MapLatLng markerLatLng = _markers.values.elementAt(index); final Icon current = Icon(Icons.location_pin); return MapMarker( latitude: markerLatLng.latitude, longitude: markerLatLng.longitude, child: GestureDetector( onTap: () { _prevSelectedMarkerIndex = _selectedMarkerIndex; _selectedMarkerIndex = index; _tileLayerController.updateMarkers([ if (_prevSelectedMarkerIndex != -1) _prevSelectedMarkerIndex, _selectedMarkerIndex ]); }, child: AnimatedContainer( transform: Matrix4.identity()..translate(0.0, -size / 2), duration: const Duration(milliseconds: 250), height: size, width: size, child: FittedBox(child: current), ), ), ); }
For more information, refer to this animating markers on Flutter Maps project on GitHub.
In this blog, we’ve learned how to animate a single marker and groups of markers at load time, dynamically, and also via interaction in the Syncfusion Flutter Maps widget. Give it a try and leave your feedback in the comments section below.
Check out other features of our Flutter Maps in the user guide and explore our Flutter Maps project samples. Additionally, check out our demo apps available on different platforms: Android, iOS, web, Windows, and Linux.
If you need a new widget for the Flutter framework or new features in our existing widgets, you can contact us through our support forums, support portal, or feedback portal. As always, we are happy to assist you!
If you like this blog post, we think you will also like the following articles too: