Copied RSS Feed

Flutter

How to Add Animated and Interactive Custom Map Markers in Flutter Maps

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:

Add an animated marker at load time

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,
            ),
          );
        },
      ),
    );
  }
}
Adding an Animated Marker During Initial Loading of Flutter Maps

Adding a marker programmatically

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),
      ),
    );
  }
}
Adding an Animated Marker Dynamically in Flutter Maps

Animate a marker or group of markers dynamically

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);
        },
      ),
    );
  }
}
Animating Markers Dynamically in Futter Maps

Animate a marker on interaction

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),
      ),
    ),
  );
}
Animate a Marker on Interaction in Flutter Maps

Resource

For more information, refer to this animating markers on Flutter Maps project on GitHub.

Conclusion

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: AndroidiOSwebWindows, 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 forumssupport portal, or feedback portal. As always, we are happy to assist you!

Related Blogs

If you like this blog post, we think you will also like the following articles too:

Meet the Author

Vijayakumar Mariappan

Vijayakumar Mariappan is a Product Manager at Syncfusion who currently manages Flutter products. He's also been a .NET developer since 2015 with expertise in Xamarin.Forms, WPF, and UWP.