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

Flutter:如何使用Flutter_map包实现地图Marker的平滑移动效果

实现方案

完全可以通过Flutter原生动画能力实现Marker的平滑过渡效果,核心思路是通过动画控制器在新旧经纬度点之间做线性插值,每一帧更新Marker的绑定坐标,而非直接替换坐标值,具体实现如下:

核心修改点

  • 新增动画控制器、插值计算相关状态变量
  • 定位更新时不再直接修改Marker坐标,而是将新坐标设为动画目标值,启动过渡动画
  • 动画过程中实时计算插值坐标刷新Marker位置
  • 可选开启地图相机的平滑移动能力,和Marker动画同步体验更一致

完整修改后代码

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart' as latLng;
import 'package:car_speed_limit/Components/constants.dart';
import 'dart:async';

//ignore: must_be_immutable
class RoadMap extends StatefulWidget {
  var userLat;
  var userLong;
  var userSpeed;
  RoadMap({this.userLat, this.userLong, this.userSpeed});
  @override
  _RoadMapState createState() => _RoadMapState();
}

class _RoadMapState extends State<RoadMap> with SingleTickerProviderStateMixin{
  MapController? _mapController;
  StreamSubscription<Position>? positionStreamSub2;
  // 新增动画相关变量
  late AnimationController _animationController;
  late Animation<double> _animation;
  latLng.LatLng? _currentMarkerPosition; // 动画过程中Marker的实时坐标
  latLng.LatLng? _previousPosition; // 上一次的定位坐标
  latLng.LatLng? _targetPosition; // 最新的目标定位坐标

  @override
  void initState() {
    super.initState();
    _mapController = MapController();
    // 初始化动画控制器,动画时长可根据需求调整
    _animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 800));
    // 可调整动画曲线实现不同的过渡效果
    _animation = CurvedAnimation(parent: _animationController, curve: Curves.easeInOut);
    // 动画监听,每一帧更新Marker坐标
    _animation.addListener(() {
      if (_previousPosition == null || _targetPosition == null) return;
      setState(() {
        // 线性插值计算当前经纬度
        final lat = _previousPosition!.latitude + (_targetPosition!.latitude - _previousPosition!.latitude) * _animation.value;
        final lng = _previousPosition!.longitude + (_targetPosition!.longitude - _previousPosition!.longitude) * _animation.value;
        _currentMarkerPosition = latLng.LatLng(lat, lng);
      });
    });
    // 首次加载初始化坐标
    _currentMarkerPosition = latLng.LatLng(widget.userLat, widget.userLong);
    _previousPosition = _currentMarkerPosition;
    _targetPosition = _currentMarkerPosition;
    
    positionStreamSub2= Geolocator.getPositionStream(
        intervalDuration: Duration(milliseconds: 1000),
        forceAndroidLocationManager: false,
        desiredAccuracy: LocationAccuracy.best).listen((_updatedPosition) {
      if (!mounted) {
        return;
      }
      // 更新坐标目标值
      _previousPosition = _targetPosition;
      _targetPosition = latLng.LatLng(_updatedPosition.latitude, _updatedPosition.longitude);
      // 重置并启动动画
      _animationController.reset();
      _animationController.forward();
      // 地图相机平滑移动,第三个参数为动画时长
      _mapController?.move(_targetPosition!, 16.8, duration: const Duration(milliseconds: 800));
      setState(() {
        widget.userLat= _updatedPosition.latitude; 
        widget.userLong= _updatedPosition.longitude;
        widget.userSpeed= (_updatedPosition.speed*(2.23693)).toInt(); 
      });
      print('THE ROAD STREAM $_updatedPosition');
    });
  }

  @override
  void dispose() {
    // 释放动画资源
    _animationController.dispose();
    positionStreamSub2?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('${widget.userSpeed}'),
        backgroundColor: Color(0xFF01051C),
        leading: IconButton(
            icon: Icon(
              Icons.arrow_back,
              color: kIconColor,
              size: 35,
            ),
            onPressed: () {
              Navigator.of(context).pop();
              positionStreamSub2?.cancel();
            }
        ),
        actions: [
          Column(
            children: [
              Text('Lat: ${widget.userLat}',
                  style: TextStyle(
                    color: kIconColor,
                  )),
              Text('Long: ${widget.userLong}',
                  style: TextStyle(
                    color: kIconColor,
                  )),
            ],
          ),
        ],
      ),
      body: FlutterMap(
          mapController: _mapController,
          options: MapOptions(
            minZoom: 2.6,
            maxZoom: 18.0,
            zoom: 17.0,
            center: _currentMarkerPosition,
            interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag | InteractiveFlag.pinchMove,
          ),
          layers: [
            TileLayerOptions(
              urlTemplate: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
              subdomains: ['a','b','c'],
            ),
            MarkerLayerOptions(
              markers: [
                Marker(
                    point: _currentMarkerPosition!,
                    builder: (context) {
                      return Stack(
                        children:[
                          Center(
                            child: Container(
                              height: 30,
                              width: 30,
                              decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                color: Colors.tealAccent.withOpacity(0.5),
                              ),
                            ),
                          ),
                          Center(
                            child: Container(
                              height: 17,
                              width: 17,
                              decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                color: Colors.tealAccent[700],
                              ),
                            ),
                          ),
                        ],
                      );
                    }
                ),
              ],
            ),
          ]
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Rash

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 20:36:02