Skip to content

Animated Annotations

Smoothly animate annotation position changes using Flutter's animation system.

Prerequisites

Annotations only work once the style has loaded and if their type is part of the widget's annotationOrder. See Prerequisites.

How it works

Annotation positions are updated via updateSymbol(). By driving updates from a Flutter AnimationController, you get smooth motion at the Flutter tick rate (typically 60fps).

Animate a symbol position

late AnimationController _animationController;
late Animation<double> _animation;
Symbol? _symbol;

@override
void initState() {
  super.initState();
  _animationController = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  );
  _animation = CurvedAnimation(
    parent: _animationController,
    curve: Curves.easeInOut,
  );
}

void _startAnimation(LatLng from, LatLng to) {
  _animation.addListener(() {
    final t = _animation.value;
    final lat = from.latitude + (to.latitude - from.latitude) * t;
    final lng = from.longitude + (to.longitude - from.longitude) * t;
    controller.updateSymbol(
      _symbol!,
      SymbolOptions(geometry: LatLng(lat, lng)),
    );
  });
  _animationController.forward(from: 0);
}

Or using lerpDouble from dart:ui:

_animationController.addListener(() {
  final lat = lerpDouble(start.latitude, end.latitude, _animation.value)!;
  final lng = lerpDouble(start.longitude, end.longitude, _animation.value)!;
  controller.updateSymbol(
    symbol,
    SymbolOptions(geometry: LatLng(lat, lng)),
  );
});
_animationController.forward();

Animate bearing (icon rotation)

_animationController.addListener(() {
  final bearing = lerpDouble(startBearing, endBearing, _animation.value)!;
  controller.updateSymbol(
    symbol,
    SymbolOptions(iconRotate: bearing),
  );
});

This is useful for animated vehicle icons that rotate to face the direction of travel.

Continuous tracking animation

For real-time tracking (GPS updates), restart the animation on each new position:

void onNewPosition(LatLng newPos) {
  final fromPos = _currentPos;
  _currentPos = newPos;
  _animationController.value = 0;
  _from = fromPos;
  _to = newPos;
  _animationController.forward();
}

Performance

updateSymbol() triggers a style update on every frame. For very fast animations or many symbols simultaneously, consider switching to Style Layers with setGeoJsonFeature(), which batches updates more efficiently.

Key APIs