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

如何实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:02:51