Animate a Point in Flutter
This tutorial shows how to animate a point (marker) bouncing, pulsing, or moving on the map using Flutter's built-in animation system.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Bouncing Marker
Animate a marker that bounces up and down continuously:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class BouncingMarkerScreen extends StatefulWidget {
@override
_BouncingMarkerScreenState createState() => _BouncingMarkerScreenState();
}
class _BouncingMarkerScreenState extends State<BouncingMarkerScreen>
with SingleTickerProviderStateMixin {
MapMetricsController? mapController;
late AnimationController _animController;
late Animation<double> _bounceAnimation;
final LatLng markerPosition = LatLng(48.8584, 2.2945); // Eiffel Tower
@override
void initState() {
super.initState();
_animController = AnimationController(
duration: Duration(milliseconds: 800),
vsync: this,
)..repeat(reverse: true);
_bounceAnimation = Tween<double>(begin: 0.0, end: 0.003).animate(
CurvedAnimation(parent: _animController, curve: Curves.easeInOut),
);
_animController.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
// Offset the latitude slightly to simulate bouncing
final animatedPosition = LatLng(
markerPosition.latitude + _bounceAnimation.value,
markerPosition.longitude,
);
return Scaffold(
appBar: AppBar(title: Text('Bouncing Marker')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: markerPosition,
zoom: 15.0,
),
markers: {
Marker(
markerId: MarkerId('bouncing'),
position: animatedPosition,
infoWindow: InfoWindow(title: 'Eiffel Tower'),
),
},
),
);
}
@override
void dispose() {
_animController.dispose();
super.dispose();
}
}Pulsing Circle
Show a pulsing circle that grows and fades around a location:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class PulsingCircleScreen extends StatefulWidget {
@override
_PulsingCircleScreenState createState() => _PulsingCircleScreenState();
}
class _PulsingCircleScreenState extends State<PulsingCircleScreen>
with SingleTickerProviderStateMixin {
MapMetricsController? mapController;
late AnimationController _animController;
late Animation<double> _radiusAnimation;
late Animation<double> _opacityAnimation;
final LatLng center = LatLng(48.8566, 2.3522); // Paris center
@override
void initState() {
super.initState();
_animController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
)..repeat();
_radiusAnimation = Tween<double>(begin: 100.0, end: 500.0).animate(
CurvedAnimation(parent: _animController, curve: Curves.easeOut),
);
_opacityAnimation = Tween<double>(begin: 0.4, end: 0.0).animate(
CurvedAnimation(parent: _animController, curve: Curves.easeOut),
);
_animController.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Pulsing Circle')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: center,
zoom: 14.0,
),
circles: {
// Pulsing circle
Circle(
circleId: CircleId('pulse'),
center: center,
radius: _radiusAnimation.value,
fillColor: Colors.blue.withOpacity(_opacityAnimation.value),
strokeColor: Colors.blue.withOpacity(_opacityAnimation.value),
strokeWidth: 2,
),
// Static center dot
Circle(
circleId: CircleId('center_dot'),
center: center,
radius: 30,
fillColor: Colors.blue.withOpacity(0.8),
strokeColor: Colors.white,
strokeWidth: 3,
),
},
markers: {
Marker(
markerId: MarkerId('center'),
position: center,
infoWindow: InfoWindow(title: 'Your Location'),
),
},
),
);
}
@override
void dispose() {
_animController.dispose();
super.dispose();
}
}Moving Point Between Locations
Smoothly move a point from one location to another:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class MovingPointScreen extends StatefulWidget {
@override
_MovingPointScreenState createState() => _MovingPointScreenState();
}
class _MovingPointScreenState extends State<MovingPointScreen>
with SingleTickerProviderStateMixin {
MapMetricsController? mapController;
late AnimationController _animController;
late Animation<double> _latAnimation;
late Animation<double> _lngAnimation;
final List<Map<String, dynamic>> destinations = [
{'name': 'Paris', 'lat': 48.8566, 'lng': 2.3522},
{'name': 'London', 'lat': 51.5074, 'lng': -0.1276},
{'name': 'Berlin', 'lat': 52.52, 'lng': 13.405},
{'name': 'Rome', 'lat': 41.9028, 'lng': 12.4964},
];
int currentDestination = 0;
@override
void initState() {
super.initState();
_animController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_setAnimationToNextDestination();
_animController.addListener(() {
setState(() {});
});
_animController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// Move to next destination
setState(() {
currentDestination =
(currentDestination + 1) % destinations.length;
});
_setAnimationToNextDestination();
_animController.forward(from: 0.0);
}
});
_animController.forward();
}
void _setAnimationToNextDestination() {
final from = destinations[currentDestination];
final to =
destinations[(currentDestination + 1) % destinations.length];
_latAnimation = Tween<double>(
begin: from['lat'],
end: to['lat'],
).animate(CurvedAnimation(
parent: _animController,
curve: Curves.easeInOut,
));
_lngAnimation = Tween<double>(
begin: from['lng'],
end: to['lng'],
).animate(CurvedAnimation(
parent: _animController,
curve: Curves.easeInOut,
));
}
@override
Widget build(BuildContext context) {
final currentPosition = LatLng(
_latAnimation.value,
_lngAnimation.value,
);
final destName = destinations[
(currentDestination + 1) % destinations.length]['name'];
return Scaffold(
appBar: AppBar(title: Text('Moving Point')),
body: Stack(
children: [
MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.0, 8.0),
zoom: 4.0,
),
markers: {
// Destination markers
...destinations.map((d) => Marker(
markerId: MarkerId(d['name']),
position: LatLng(d['lat'], d['lng']),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueOrange),
infoWindow: InfoWindow(title: d['name']),
)),
// Moving point
Marker(
markerId: MarkerId('moving'),
position: currentPosition,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueBlue),
),
},
),
// Status bar
Positioned(
top: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: EdgeInsets.all(12),
child: Text(
'Moving to $destName...',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
),
],
),
);
}
@override
void dispose() {
_animController.dispose();
super.dispose();
}
}Animation Options
| Type | Use Case | Flutter Class |
|---|---|---|
| Bounce | Draw attention to a marker | AnimationController + Curves.easeInOut |
| Pulse | Show live location or alerts | AnimationController + repeat() |
| Move | Transition between locations | Tween<double> for lat/lng |
| Rotate | Compass or direction indicator | Tween<double> for bearing |
Next Steps
- Animate Point Along Route — Move along a path
- Animate a Marker — More marker animations
- Animate a Line — Progressive line drawing
Tip: Use SingleTickerProviderStateMixin for a single animation or TickerProviderStateMixin if your screen has multiple independent animations running simultaneously.