如何实现Flutter Google Maps与flutter_map的地图状态同步?
实现Google Maps与flutter_map的位置/折线同步方案
要实现两款地图组件间的标记、折线同步,核心思路是统一状态管理+坐标格式兼容转换,以下是具体实现步骤和代码示例:
1. 依赖准备
先在pubspec.yaml中添加所需依赖:
dependencies: flutter: sdk: flutter google_maps_flutter: ^2.5.0 flutter_map: ^6.1.0 latlong2: ^0.9.0 geolocator: ^10.1.0 provider: ^6.1.1
2. 全局状态管理
创建共享状态类,统一存储需要同步的地图数据,同时提供坐标转换方法:
import 'package:flutter/foundation.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' as gm; import 'package:latlong2/latlong.dart' as lm; class MapSyncState extends ChangeNotifier { // 当前定位坐标(Google Maps 类型) gm.LatLng? currentLocation; // 标记点列表 List<gm.LatLng> markers = []; // 折线坐标列表 List<gm.LatLng> polylinePoints = []; // 更新定位并同步标记 void updateLocation(gm.LatLng location) { currentLocation = location; markers = [location]; // 单标记场景,多标记可扩展为列表操作 notifyListeners(); } // 添加折线点 void addPolylinePoint(gm.LatLng point) { polylinePoints.add(point); notifyListeners(); } // 清空折线 void clearPolyline() { polylinePoints.clear(); notifyListeners(); } // Google Maps 坐标转 flutter_map 坐标 lm.LatLng convertToLM(gm.LatLng gmLatLng) { return lm.LatLng(gmLatLng.latitude, gmLatLng.longitude); } // flutter_map 坐标转 Google Maps 坐标 gm.LatLng convertToGM(lm.LatLng lmLatLng) { return gm.LatLng(lmLatLng.latitude, lmLatLng.longitude); } }
在main.dart中用Provider包裹整个应用,让所有组件共享状态:
void main() { runApp( ChangeNotifierProvider( create: (context) => MapSyncState(), child: const MyApp(), ), ); }
3. Google Maps 端实现(含初始定位)
实现Google Maps组件,完成定位、标记渲染、折线绘制及地图切换逻辑:
import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:provider/provider.dart'; import 'package:geolocator/geolocator.dart'; import 'flutter_map_view.dart'; import 'map_sync_state.dart'; class GoogleMapView extends StatefulWidget { const GoogleMapView({super.key}); @override State<GoogleMapView> createState() => _GoogleMapViewState(); } class _GoogleMapViewState extends State<GoogleMapView> { late GoogleMapController _mapController; final GeolocatorPlatform _geolocator = GeolocatorPlatform.instance; @override void initState() { super.initState(); _getCurrentLocation(); } // 获取当前定位并更新状态 Future<void> _getCurrentLocation() async { // 检查定位服务是否开启 bool serviceEnabled = await _geolocator.isLocationServiceEnabled(); if (!serviceEnabled) return; // 检查并请求定位权限 LocationPermission permission = await _geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await _geolocator.requestPermission(); if (permission == LocationPermission.denied) return; } // 获取定位并同步到全局状态 Position position = await _geolocator.getCurrentPosition(); final LatLng location = LatLng(position.latitude, position.longitude); Provider.of<MapSyncState>(context, listen: false).updateLocation(location); // 移动相机到定位位置 _mapController.animateCamera(CameraUpdate.newLatLng(location)); } @override Widget build(BuildContext context) { return Scaffold( body: Consumer<MapSyncState>( builder: (context, state, child) { return GoogleMap( onMapCreated: (controller) => _mapController = controller, initialCameraPosition: CameraPosition( target: state.currentLocation ?? const LatLng(0, 0), zoom: 15, ), // 渲染标记 markers: Set<Marker>.from( state.markers.map((latLng) => Marker( markerId: const MarkerId('current-location'), position: latLng, icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed), )), ), // 渲染折线 polylines: Set<Polyline>.from([ Polyline( polylineId: const PolylineId('route'), points: state.polylinePoints, color: Colors.blue, width: 3, ), ]), // 点击地图添加折线点 onTap: (latLng) { state.addPolylinePoint(latLng); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const FlutterMapView())); }, child: const Icon(Icons.swap_horiz), ), ); } }
4. flutter_map 端同步实现
读取全局状态,渲染对应标记和折线,保持与Google Maps的同步:
import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; import 'map_sync_state.dart'; class FlutterMapView extends StatelessWidget { const FlutterMapView({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Consumer<MapSyncState>( builder: (context, state, child) { final LatLng? target = state.currentLocation != null ? state.convertToLM(state.currentLocation!) : const LatLng(0, 0); return FlutterMap( options: MapOptions( initialCenter: target!, initialZoom: 15, // 点击地图添加折线点(同步到全局状态) onTap: (_, latLng) { state.addPolylinePoint(state.convertToGM(latLng)); }, ), children: [ // OpenStreetMap 瓦片层 TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: const ['a', 'b', 'c'], ), // 渲染标记 MarkerLayer( markers: state.markers.map((gmLatLng) => Marker( width: 40, height: 40, point: state.convertToLM(gmLatLng), child: const Icon(Icons.location_on, color: Colors.red, size: 40), )).toList(), ), // 渲染折线 PolylineLayer( polylines: [ Polyline( points: state.polylinePoints.map((gmLatLng) => state.convertToLM(gmLatLng)).toList(), color: Colors.blue, strokeWidth: 3, ), ], ), ], ); }, ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.pop(context); }, child: const Icon(Icons.swap_horiz), ), ); } }
关键注意事项
- 坐标转换:两款地图库的
LatLng类属于不同包,必须通过转换方法保证坐标一致,避免位置偏移。 - 状态更新:所有修改位置、标记、折线的操作都需通过状态类方法完成,并调用
notifyListeners()触发UI更新。 - 权限处理:定位前必须检查并请求位置权限,否则无法获取当前位置。
- 相机同步:切换地图时,保持相机中心和缩放级别一致,提升用户体验。
内容的提问来源于stack exchange,提问作者Md. Samiul Islam
相关产品推荐
相关产品推荐

