Flutter实现实时平滑缩减折线,无需重复调用Directions API
实现无需重复调用API的折线平滑缩减效果
核心思路是:一次性获取完整路线坐标后,在本地实时匹配用户位置与路线,动态裁剪折线并配合动画实现平滑过渡,完全复刻谷歌地图导航的折线更新逻辑。
具体实现步骤
1. 预存完整路线坐标
调用一次Directions API后,将返回的编码折线(encoded polyline)解码为完整的经纬度列表,存储在本地变量中,后续所有折线更新都基于这个列表操作,不再重复请求API。
2. 实时匹配用户位置到路线
当用户位置更新时,通过距离计算找到用户当前位置在预存路线上的最近点,确定该点在路线列表中的索引。这里可以用Haversine公式计算两点间直线距离,也可以结合地图SDK的投影工具提升匹配精度。
3. 动态生成剩余路线折线
以用户当前位置为起点,拼接从最近点索引到终点的路线坐标,组成新的折线点列表。如果用户位置不在预存路线的节点上,直接把当前位置作为折线起点,保证显示的路线始终贴合用户实际位置。
4. 添加平滑过渡动画
利用你已有的TickerProviderStateMixin能力,给折线的点列表添加补间动画。通过AnimationController和Tween实现旧折线到新折线的平滑过渡,让折线缩减效果自然流畅,而非瞬间切换。
代码示例(Flutter + google_maps_flutter)
import 'dart:math'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:flutter/material.dart'; class NavigationMap extends StatefulWidget { const NavigationMap({super.key}); @override State<NavigationMap> createState() => _NavigationMapState(); } class _NavigationMapState extends State<NavigationMap> with TickerProviderStateMixin { List<LatLng> _fullRoutePoints = []; List<LatLng> _currentRoutePoints = []; AnimationController? _lineAnimationController; Animation<List<LatLng>>? _lineAnimation; @override void initState() { super.initState(); _lineAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), // 可根据需求调整动画时长 ); // 假设这里已通过API获取到encodedPolyline并解码 // _decodeRoute(encodedPolylineFromAPI); } @override void dispose() { _lineAnimationController?.dispose(); super.dispose(); } // 解码API返回的编码折线 void _decodeRoute(String encodedPolyline) { _fullRoutePoints = PolylineUtil.decodePolyline(encodedPolyline); _currentRoutePoints = List.from(_fullRoutePoints); } // 用户位置更新时触发的处理逻辑 void _handleUserLocationUpdate(LatLng newUserLocation) { // 找到用户位置在完整路线上的最近点索引 int closestPointIndex = _findClosestRoutePointIndex(newUserLocation); // 生成新的剩余路线点:用户当前位置 + 后续路线节点 List<LatLng> newRoutePoints = [newUserLocation]; if (closestPointIndex < _fullRoutePoints.length - 1) { newRoutePoints.addAll(_fullRoutePoints.sublist(closestPointIndex)); } // 启动折线过渡动画 _lineAnimationController?.reset(); _lineAnimation = Tween<List<LatLng>>( begin: _currentRoutePoints, end: newRoutePoints, ).animate(CurvedAnimation( parent: _lineAnimationController!, curve: Curves.easeInOut, )); _lineAnimation?.addListener(() { if (mounted) { setState(() { _currentRoutePoints = _lineAnimation!.value; }); } }); _lineAnimationController?.forward(); } // 查找路线上距离用户位置最近的点的索引 int _findClosestRoutePointIndex(LatLng targetLocation) { double minDistance = double.infinity; int closestIndex = 0; // 优化:可以只遍历用户当前位置附近的路线段,提升性能 for (int i = 0; i < _fullRoutePoints.length; i++) { double distance = _calculateDistance(targetLocation, _fullRoutePoints[i]); if (distance < minDistance) { minDistance = distance; closestIndex = i; } } return closestIndex; } // Haversine公式计算两点间距离(单位:米) double _calculateDistance(LatLng a, LatLng b) { const double earthRadius = 6371000; double dLat = _degToRad(b.latitude - a.latitude); double dLon = _degToRad(b.longitude - a.longitude); double lat1 = _degToRad(a.latitude); double lat2 = _degToRad(b.latitude); double aVal = sin(dLat / 2) * sin(dLat / 2) + sin(dLon / 2) * sin(dLon / 2) * cos(lat1) * cos(lat2); double c = 2 * atan2(sqrt(aVal), sqrt(1 - aVal)); return earthRadius * c; } double _degToRad(double degrees) => degrees * pi / 180; @override Widget build(BuildContext context) { return GoogleMap( polylines: { Polyline( polylineId: const PolylineId('navigation_route'), points: _currentRoutePoints, width: 5, color: Colors.blue, ), }, // 其他地图配置... ); } }
注意事项
- 性能优化:如果路线很长,遍历所有点计算距离会影响性能,可以限制只在用户当前位置附近的路线范围内查找最近点,比如只检查前后20个节点。
- 路线偏离处理:当用户偏离预存路线超过阈值(比如50米)时,再重新调用Directions API获取新路线,否则一直用本地逻辑处理。
- 动画适配:可以根据用户移动的距离动态调整动画时长,移动距离越远,动画时长稍长,让过渡更自然。
内容的提问来源于stack exchange,提问作者Smith
相关产品推荐
相关产品推荐

