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
相关产品推荐
相关产品推荐

