11package com.mapbox.maps.mapbox_maps
22
3+ import android.annotation.SuppressLint
34import android.content.Context
45import android.graphics.Bitmap
6+ import android.util.AttributeSet
7+ import android.util.Log
8+ import android.view.MotionEvent
59import android.view.View
610import androidx.lifecycle.DefaultLifecycleObserver
711import androidx.lifecycle.Lifecycle
@@ -38,6 +42,52 @@ import io.flutter.plugin.common.MethodChannel
3842import io.flutter.plugin.platform.PlatformView
3943import java.io.ByteArrayOutputStream
4044
45+ class FlutterMapView : MapView {
46+ constructor (context: Context , mapInitOptions: MapInitOptions ) : super (context, mapInitOptions)
47+ constructor (context: Context , attrs: AttributeSet ? ) : super (context, attrs)
48+
49+ // Track expected pointer count to detect wrong events
50+ private var expectedPointerCount = 0
51+
52+ // Here we have workaround for issue introduced in Flutter SDK 3.19,
53+ // when map widget is nested inside a gesture detector with e.g. long press callbacks,
54+ // then platform view receives following motion event sequence for a zoom(pinch) gesture:
55+ // DOWN -> MOVE(3-5 events) -> POINTER_DOWN -> MOVE(duplicates of previous move events) -> MOVE(new move events) -> POINTER_UP -> UP
56+ // While the correct sequence is:
57+ // DOWN -> POINTER_DOWN -> MOVE -> POINTER_UP -> UP
58+ @SuppressLint(" ClickableViewAccessibility" )
59+ override fun onTouchEvent (event : MotionEvent ): Boolean {
60+ val actionMasked = event.actionMasked
61+ val actionString = MotionEvent .actionToString(event.action)
62+ val pointerCount = event.pointerCount
63+
64+ // For MOVE events, check if pointer count matches expected
65+ // This filters out both the early MOVE events AND their duplicates
66+ if (actionMasked == MotionEvent .ACTION_MOVE && expectedPointerCount > 0 && pointerCount != expectedPointerCount) {
67+ Log .d(" NativeView" , " ✗ Filtered: $actionString , Expected: $expectedPointerCount , Got: $pointerCount , Time: ${event.eventTime} " )
68+ return false // Skip wrong count
69+ }
70+
71+ // Update expected pointer count based on action
72+ when (actionMasked) {
73+ MotionEvent .ACTION_DOWN -> {
74+ expectedPointerCount = 1
75+ }
76+ MotionEvent .ACTION_POINTER_DOWN -> {
77+ expectedPointerCount = pointerCount
78+ }
79+ MotionEvent .ACTION_POINTER_UP -> {
80+ expectedPointerCount = pointerCount - 1
81+ }
82+ MotionEvent .ACTION_UP -> {
83+ expectedPointerCount = 0
84+ }
85+ }
86+
87+ return super .onTouchEvent(event)
88+ }
89+ }
90+
4191class MapboxMapController (
4292 context : Context ,
4393 mapInitOptions : MapInitOptions ,
@@ -50,7 +100,7 @@ class MapboxMapController(
50100 DefaultLifecycleObserver ,
51101 MethodChannel .MethodCallHandler {
52102
53- private var mapView: MapView ? = null
103+ private var mapView: FlutterMapView ? = null
54104 private var mapboxMap: MapboxMap ? = null
55105
56106 private val methodChannel: MethodChannel
@@ -136,7 +186,7 @@ class MapboxMapController(
136186 this .messenger = messenger
137187 this .channelSuffix = channelSuffix.toString()
138188
139- val mapView = MapView (context, mapInitOptions)
189+ val mapView = FlutterMapView (context, mapInitOptions)
140190 val mapboxMap = mapView.mapboxMap
141191 this .mapView = mapView
142192 this .mapboxMap = mapboxMap
0 commit comments