You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:27:06