如何在Flutter的Google Map中为Marker添加跳跃动画效果?
在Flutter Google Map中实现Marker脉冲缩放动画
要实现动图里的Marker弹性缩放(脉冲)效果,核心思路是自定义带动画的Widget,再将其转换为Google Map Marker支持的BitmapDescriptor,具体步骤和代码如下:
1. 依赖准备
首先确保pubspec.yaml中引入最新版的Google Map包:
dependencies: flutter: sdk: flutter google_maps_flutter: ^2.5.0
2. 实现带动画的Marker Widget
创建一个支持缩放/脉冲动画的自定义Widget,这里用AnimationController结合ScaleTransition实现弹性缩放效果:
class AnimatedMarker extends StatefulWidget { final Color color; final double size; const AnimatedMarker({super.key, required this.color, this.size = 40}); @override State<AnimatedMarker> createState() => _AnimatedMarkerState(); } class _AnimatedMarkerState extends State<AnimatedMarker> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation; @override void initState() { super.initState(); // 初始化动画控制器,800ms一个周期,循环反向播放实现回弹 _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), )..repeat(reverse: true); // 缩放动画:从1倍放大到1.4倍 _scaleAnimation = Tween<double>(begin: 1.0, end: 1.4).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ScaleTransition( scale: _scaleAnimation, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( color: widget.color, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: widget.color.withOpacity(0.5), blurRadius: 8, spreadRadius: 2, ) ], ), child: const Center( child: Icon( Icons.location_on, color: Colors.white, size: 20, ), ), ), ); } }
如果需要更贴近动图的外围扩散脉冲效果,可以修改build方法,叠加一个渐隐的缩放背景圈:
@override Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children: [ // 外围扩散渐隐圈 ScaleTransition( scale: Tween<double>(begin: 1.0, end: 2.0).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOut), ), child: Opacity( opacity: Tween<double>(begin: 0.5, end: 0.0).animate(_controller).value, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( color: widget.color, shape: BoxShape.circle, ), ), ), ), // 中心缩放Marker ScaleTransition( scale: _scaleAnimation, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( color: widget.color, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: widget.color.withOpacity(0.5), blurRadius: 8, spreadRadius: 2, ) ], ), child: const Center( child: Icon( Icons.location_on, color: Colors.white, size: 20, ), ), ), ), ], ); }
3. 在Google Map中使用动画Marker
将自定义的动画Widget转换为BitmapDescriptor,再赋值给Marker的icon属性:
class MapScreen extends StatefulWidget { const MapScreen({super.key}); @override State<MapScreen> createState() => _MapScreenState(); } class _MapScreenState extends State<MapScreen> { late GoogleMapController _mapController; BitmapDescriptor? _animatedMarkerIcon; // 初始地图位置(示例:旧金山) final CameraPosition _initialCamera = const CameraPosition( target: LatLng(37.7749, -122.4194), zoom: 14, ); @override void initState() { super.initState(); _createAnimatedMarker(); } // 异步生成动画Marker的BitmapDescriptor Future<void> _createAnimatedMarker() async { _animatedMarkerIcon = await BitmapDescriptor.fromWidget( const AnimatedMarker(color: Colors.blue), ); setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( initialCameraPosition: _initialCamera, onMapCreated: (controller) => _mapController = controller, markers: _animatedMarkerIcon != null ? { Marker( markerId: const MarkerId('animated_marker'), position: const LatLng(37.7749, -122.4194), icon: _animatedMarkerIcon!, ) } : {}, ), ); } }
关键注意事项
BitmapDescriptor.fromWidget是异步方法,必须等待初始化完成后再渲染Marker。- 若不需要循环动画,可去掉
_controller.repeat(reverse: true),改用_controller.forward()在需要时触发单次动画。 - 大量动画Marker可能影响性能,建议复用
AnimationController或根据可视范围动态创建/销毁动画。
内容的提问来源于stack exchange,提问作者abdul
相关产品推荐
相关产品推荐

