Flutter GoogleMap捏合缩放如何禁止触发onCameraMove/onCameraIdle
开发环境
- Flutter版本:3.0.2
- Dart版本:2.17.3
依赖配置
google_maps_flutter: ^2.1.8
问题背景
现有代码运行时存在两个不符合预期的问题:
- 对GoogleMap执行捏合缩放操作会触发
onCameraMove、onCameraIdle回调 - 缩放效果未以当前手指操作位置为基准生效,交互体验异常
附原有问题代码:
Completer<GoogleMapController> _controllerGoogleMap = Completer(); GoogleMapController? _googleMapController; Stack( children: [ GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(12.9716, 77.5946), zoom: 18, ), minMaxZoomPreference: MinMaxZoomPreference(8, 19), zoomControlsEnabled: false, zoomGesturesEnabled: true, scrollGesturesEnabled: true, mapToolbarEnabled: false, rotateGesturesEnabled: false, tiltGesturesEnabled: false, myLocationEnabled: true, mapType: MapType.normal, compassEnabled: false, onMapCreated: onMapCreated, onCameraMove: onCameraMove, onCameraIdle: onCameraIdle, gestureRecognizers: Set() ..add(Factory<PanGestureRecognizer>( () => PanGestureRecognizer())), ), Center( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: width * 0.15, height: width * 0.05, decoration: BoxDecoration( color: Colors.white, border: Border.all(width: 2, color: Colors.black), shape: BoxShape.circle), child: Padding( padding: const EdgeInsets.all(2.0), child: Container( decoration: BoxDecoration( color: Colors.green, shape: BoxShape.circle), ), ), ), Container( width: width * 0.006, height: height * 0.025, decoration: BoxDecoration( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(5.0), bottomRight: Radius.circular(5.0), ), color: Colors.black), ), SizedBox(height: 30,), ], ), ), ], ); void _onMapCreated(GoogleMapController controller){ _controllerGoogleMap.complete(controller); _googleMapController = controller; } void onCameraMove(){ print("On Camera Move method"); } void onCameraIdle(){ print("On Camera Idle method"); }
可行解决方案
核心逻辑:禁用google_maps_flutter内置的缩放手势,自行接管双指捏合交互,通过状态标记屏蔽缩放过程中触发的相机回调,同时基于触点坐标计算缩放基准点,解决缩放偏移问题。
实现步骤
- 新增状态变量用于记录缩放状态
// 标记当前是否为主动执行的缩放操作,用于屏蔽相机回调 bool _isProgrammaticZoom = false; // 记录缩放起始的地图缩放级别 double _initialZoom = 18; // 记录双指操作起始的触点间距 double _initialScaleDistance = 0;
- 修改GoogleMap组件配置,禁用原生缩放手势,在相机回调中增加状态判断
GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(12.9716, 77.5946), zoom: 18, ), minMaxZoomPreference: MinMaxZoomPreference(8, 19), zoomControlsEnabled: false, // 禁用原生缩放手势,避免默认逻辑自动触发相机回调 zoomGesturesEnabled: false, scrollGesturesEnabled: true, mapToolbarEnabled: false, rotateGesturesEnabled: false, tiltGesturesEnabled: false, myLocationEnabled: true, mapType: MapType.normal, compassEnabled: false, // 修正原有方法名绑定不匹配问题 onMapCreated: _onMapCreated, onCameraMove: (position) { // 主动缩放场景下跳过原有业务逻辑 if (_isProgrammaticZoom) return; onCameraMove(); }, onCameraIdle: () { // 主动缩放场景下跳过原有业务逻辑 if (_isProgrammaticZoom) { _isProgrammaticZoom = false; return; } onCameraIdle(); }, gestureRecognizers: Set() ..add(Factory<PanGestureRecognizer>( () => PanGestureRecognizer())), )
- 用
GestureDetector包裹地图组件,自行实现双指缩放逻辑
GestureDetector( onScaleStart: (details) { // 双指操作开始,初始化缩放参数 _initialScaleDistance = 0; _googleMapController?.getZoomLevel().then((value) { _initialZoom = value; }); }, onScaleUpdate: (details) async { // 仅响应双指操作,忽略单指拖动 if (details.pointerCount != 2) return; // 计算双指实时间距 final currentDistance = (details.focalPoint - details.localFocalPoint).distance; if (_initialScaleDistance == 0) { _initialScaleDistance = currentDistance; return; } // 计算缩放比例,系数2可根据实际交互手感调整 final scale = currentDistance / _initialScaleDistance; final targetZoom = (_initialZoom + (scale - 1) * 2).clamp(8.0, 19.0); // 标记为主动缩放,拦截相机回调 _isProgrammaticZoom = true; // 将双指中心点的屏幕坐标转为地图经纬度,作为缩放基准点 final screenCoord = ScreenCoordinate( x: details.localFocalPoint.dx.toInt(), y: details.localFocalPoint.dy.toInt(), ); final targetLatLng = await _googleMapController?.getLatLng(screenCoord); // 执行缩放 _googleMapController?.moveCamera( CameraUpdate.newCameraPosition( CameraPosition( target: targetLatLng ?? const LatLng(12.9716, 77.5946), zoom: targetZoom, ), ), ); }, onScaleEnd: (details) { // 延迟重置标记,避免最后一次相机回调漏拦截 Future.delayed(const Duration(milliseconds: 100), () { _isProgrammaticZoom = false; }); }, child: Stack( children: [ // 放置修改配置后的GoogleMap组件 // 放置原有中心定位标记组件 ], ), )
方案说明
- 完全接管缩放逻辑后,捏合操作不会触发原有
onCameraMove、onCameraIdle绑定的业务代码,仅在用户拖动地图时正常触发回调 - 基于双指触点计算缩放基准经纬度,实现以手指操作位置为中心的缩放效果,交互和原生地图体验一致
- 缩放级别做了8-19的边界限制,和原有
minMaxZoomPreference配置保持一致 - 缩放速度系数可根据产品需求自行调整,数值越大缩放速度越快
内容的提问来源于stack exchange,提问作者Sharath Appesh
相关产品推荐
相关产品推荐

