Animate a Marker in Flutter
This tutorial shows how to smoothly animate a marker's position along a path or between waypoints.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Marker Animation
Animate a marker along a predefined route using AnimationController:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class AnimateMarkerScreen extends StatefulWidget {
@override
_AnimateMarkerScreenState createState() => _AnimateMarkerScreenState();
}
class _AnimateMarkerScreenState extends State<AnimateMarkerScreen>
with SingleTickerProviderStateMixin {
MapMetricsController? mapController;
late AnimationController _animationController;
LatLng currentPosition = LatLng(48.8584, 2.2945);
bool isAnimating = false;
// Route waypoints (Paris landmarks)
final List<LatLng> route = [
LatLng(48.8584, 2.2945), // Eiffel Tower
LatLng(48.8606, 2.3376), // Louvre
LatLng(48.8530, 2.3499), // Notre-Dame
LatLng(48.8867, 2.3431), // Sacré-Cœur
LatLng(48.8738, 2.2950), // Arc de Triomphe
LatLng(48.8584, 2.2945), // Back to Eiffel Tower
];
Set<Marker> get markers => {
Marker(
markerId: MarkerId('moving'),
position: currentPosition,
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
infoWindow: InfoWindow(title: 'Moving Marker'),
),
};
// Show the route as a polyline
Set<Polyline> get polylines => {
Polyline(
polylineId: PolylineId('route'),
points: route,
color: Colors.blue.withOpacity(0.5),
width: 3,
patterns: [PatternItem.dash(10), PatternItem.gap(8)],
),
};
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 15), // Total animation time
);
_animationController.addListener(_updateMarkerPosition);
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() => isAnimating = false);
}
});
}
void _updateMarkerPosition() {
final progress = _animationController.value;
final totalSegments = route.length - 1;
final segmentProgress = progress * totalSegments;
final segmentIndex = segmentProgress.floor().clamp(0, totalSegments - 1);
final t = segmentProgress - segmentIndex;
final from = route[segmentIndex];
final to = route[segmentIndex + 1];
setState(() {
currentPosition = LatLng(
from.latitude + (to.latitude - from.latitude) * t,
from.longitude + (to.longitude - from.longitude) * t,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Animate Marker')),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(48.8620, 2.3200),
zoom: 13.0,
),
markers: markers,
polylines: polylines,
),
// Controls
Positioned(
bottom: 24,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton.extended(
heroTag: 'play',
onPressed: isAnimating ? _stopAnimation : _startAnimation,
icon: Icon(isAnimating ? Icons.stop : Icons.play_arrow),
label: Text(isAnimating ? 'Stop' : 'Start'),
),
SizedBox(width: 12),
FloatingActionButton.small(
heroTag: 'reset',
onPressed: _resetAnimation,
child: Icon(Icons.replay),
),
],
),
),
],
),
);
}
void _startAnimation() {
setState(() => isAnimating = true);
_animationController.forward();
}
void _stopAnimation() {
_animationController.stop();
setState(() => isAnimating = false);
}
void _resetAnimation() {
_animationController.reset();
setState(() {
isAnimating = false;
currentPosition = route.first;
});
}
@override
void dispose() {
_animationController.dispose();
mapController?.dispose();
super.dispose();
}
}Looping Animation
Make the marker loop continuously along the route:
dart
void _startLoopAnimation() {
setState(() => isAnimating = true);
_animationController.repeat(); // Loops forever
}Camera Follows Marker
Keep the camera centered on the moving marker:
dart
void _updateMarkerPosition() {
// ... calculate currentPosition as above ...
setState(() {
currentPosition = interpolatedPosition;
});
// Camera follows the marker
mapController?.moveCamera(
CameraUpdate.newLatLng(currentPosition),
);
}Easing Curves
Use different animation curves for natural movement:
dart
// In initState, wrap with a CurvedAnimation:
final curvedAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut, // Smooth start and end
);
curvedAnimation.addListener(() {
final progress = curvedAnimation.value;
// ... use progress for interpolation
});| Curve | Effect |
|---|---|
Curves.linear | Constant speed (default) |
Curves.easeInOut | Slow start and end, fast middle |
Curves.easeIn | Slow start, fast end |
Curves.easeOut | Fast start, slow end |
Curves.bounceOut | Bounce effect at the end |
Next Steps
- Animate Camera Around Point — Orbiting camera animation
- Fly to a Location — Animated camera transitions
- Markers and Annotations — Static marker features
Tip: For smooth marker animation, use SingleTickerProviderStateMixin and keep the AnimationController duration proportional to the route length. Shorter routes need shorter durations.