Flutter谷歌地图如何实现Marker位置的平滑插值动画?
Flutter谷歌地图Marker平滑移动实现方案
实现原理
- 基于Flutter动画控制器
AnimationController对新旧经纬度坐标做线性插值 - 逐帧更新对应Marker的
position属性,替代直接替换坐标的逻辑 - 可选:计算移动方位角同步旋转Marker图标,还原网约车车头朝向效果
前置准备:声明所需变量
// 存储现有所有Marker,key为MarkerId Map<MarkerId, Marker> markers = {}; // 动画控制器,可根据需求调整动画时长,这里设为1500ms匹配网约车移动效果 late AnimationController _animationController; // 坐标插值动画 late Animation<LatLng> _positionAnimation;
注意:当前页面的State类需要混入
TickerProviderStateMixin才能正常初始化动画控制器。
初始化&释放动画控制器
@override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1500), ); } // 页面销毁时释放控制器避免内存泄漏 @override void dispose() { _animationController.dispose(); super.dispose(); }
自定义LatLng插值类(必选)
默认Tween不支持LatLng类型的插值计算,需要手动实现:
class LatLngTween extends Tween<LatLng> { LatLngTween({required LatLng begin, required LatLng end}) : super(begin: begin, end: end); @override LatLng lerp(double t) { double lat = begin!.latitude + (end!.latitude - begin!.latitude) * t; double lng = begin!.longitude + (end!.longitude - begin!.longitude) * t; return LatLng(lat, lng); } }
核心平滑更新逻辑
替换你原有直接创建Marker的代码,改为先判断Marker是否已存在,存在就走动画更新逻辑,不存在则直接添加:
double latitude = double.parse(value.docs[b]['lat']); double longitude = double.parse(value.docs[b]['lon']); var markerIdVal = value.docs[b].id; final MarkerId markerId = MarkerId(markerIdVal); LatLng newPosition = LatLng(latitude, longitude); // 检查该Marker是否已存在 if (markers.containsKey(markerId)) { // 拿到旧坐标作为动画起点 LatLng oldPosition = markers[markerId]!.position; // 重置并启动动画 _animationController.reset(); _positionAnimation = LatLngTween( begin: oldPosition, end: newPosition, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.linear, )); // 监听动画帧,逐帧更新Marker位置 _positionAnimation.addListener(() { final Marker updatedMarker = markers[markerId]!.copyWith( positionParam: _positionAnimation.value, // 可选:同步更新旋转角度,需要先实现下方的getBearing方法 // rotationParam: getBearing(oldPosition, newPosition), ); setState(() { markers[markerId] = updatedMarker; }); }); _animationController.forward(); } else { // 新Marker直接添加 final Marker marker = Marker( markerId: markerId, position: newPosition, icon: carIcon, ); setState(() { markers[markerId] = marker; }); }
可选:方位角计算工具方法(实现车头朝向)
如果需要图标自动朝向移动方向,添加如下工具方法:
import 'dart:math' as math; // 计算两个坐标之间的方位角,返回0-360度 double getBearing(LatLng start, LatLng end) { double lat1 = start.latitude * math.pi / 180; double lng1 = start.longitude * math.pi / 180; double lat2 = end.latitude * math.pi / 180; double lng2 = end.longitude * math.pi / 180; double dLng = (lng2 - lng1); double y = math.sin(dLng) * math.cos(lat2); double x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dLng); double bearing = (math.atan2(y, x) * 180 / math.pi); return (bearing + 360) % 360; }
注意事项
- 动画时长可根据Firestore数据更新频率调整,更新频率高的话可缩短到800-1200ms,避免动画跟不上坐标更新
- 多个Marker同时更新时可给每个Marker单独绑定动画实例,避免互相干扰
- 如果坐标更新间隔过短,可判断新坐标和当前动画目标坐标差值小于阈值时跳过重复触发动画
内容的提问来源于stack exchange,提问作者user14864926
相关产品推荐
相关产品推荐

