expo-gaode-map-navigation 1.1.5-next.1 → 1.1.5-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/android/build.gradle +10 -0
  2. package/android/src/main/cpp/CMakeLists.txt +24 -0
  3. package/android/src/main/cpp/cluster_jni.cpp +848 -0
  4. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +616 -92
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +493 -0
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +230 -14
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +37 -27
  8. package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +494 -0
  9. package/android/src/main/java/expo/modules/gaodemap/map/companion/BitmapDescriptorCache.kt +30 -0
  10. package/android/src/main/java/expo/modules/gaodemap/map/companion/IconBitmapCache.kt +37 -0
  11. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +76 -0
  12. package/android/src/main/java/expo/modules/gaodemap/map/modules/LocationManager.kt +15 -3
  13. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +4 -59
  14. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleView.kt +9 -12
  15. package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleViewModule.kt +5 -6
  16. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +539 -66
  17. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterViewModule.kt +17 -1
  18. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +165 -33
  19. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +15 -3
  20. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +1249 -672
  21. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +40 -17
  22. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointView.kt +177 -22
  23. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointViewModule.kt +11 -3
  24. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonView.kt +57 -14
  25. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonViewModule.kt +9 -5
  26. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineView.kt +90 -63
  27. package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineViewModule.kt +7 -3
  28. package/android/src/main/java/expo/modules/gaodemap/map/services/LocationForegroundService.kt +3 -2
  29. package/android/src/main/java/expo/modules/gaodemap/map/utils/BitmapDescriptorCache.kt +20 -0
  30. package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +13 -0
  31. package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +20 -0
  32. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +515 -0
  33. package/android/src/main/java/expo/modules/gaodemap/map/utils/LatLngParser.kt +91 -0
  34. package/android/src/main/java/expo/modules/gaodemap/map/utils/PermissionHelper.kt +248 -0
  35. package/build/ExpoGaodeMapNaviView.d.ts +7 -7
  36. package/build/ExpoGaodeMapNaviView.js +8 -8
  37. package/build/index.d.ts +1 -1
  38. package/build/index.js +2 -2
  39. package/build/map/ExpoGaodeMapModule.d.ts +2 -201
  40. package/build/map/ExpoGaodeMapModule.js +584 -14
  41. package/build/map/ExpoGaodeMapOfflineModule.d.ts +139 -0
  42. package/build/map/ExpoGaodeMapOfflineModule.js +8 -0
  43. package/build/map/ExpoGaodeMapView.js +66 -58
  44. package/build/map/components/FoldableMapView.d.ts +38 -0
  45. package/build/map/components/FoldableMapView.js +209 -0
  46. package/build/map/components/MapContext.d.ts +12 -0
  47. package/build/map/components/MapContext.js +54 -0
  48. package/build/map/components/MapUI.d.ts +18 -0
  49. package/build/map/components/MapUI.js +29 -0
  50. package/build/map/components/overlays/Circle.js +34 -3
  51. package/build/map/components/overlays/Cluster.d.ts +3 -1
  52. package/build/map/components/overlays/Cluster.js +31 -2
  53. package/build/map/components/overlays/HeatMap.d.ts +3 -1
  54. package/build/map/components/overlays/HeatMap.js +33 -3
  55. package/build/map/components/overlays/Marker.d.ts +1 -1
  56. package/build/map/components/overlays/Marker.js +37 -32
  57. package/build/map/components/overlays/MultiPoint.js +1 -1
  58. package/build/map/components/overlays/Polygon.js +30 -3
  59. package/build/map/components/overlays/Polyline.js +36 -3
  60. package/build/map/index.d.ts +25 -5
  61. package/build/map/index.js +59 -18
  62. package/build/map/types/common.types.d.ts +40 -0
  63. package/build/map/types/common.types.js +0 -4
  64. package/build/map/types/index.d.ts +3 -2
  65. package/build/map/types/map-view.types.d.ts +108 -3
  66. package/build/map/types/native-module.types.d.ts +363 -0
  67. package/build/map/types/native-module.types.js +5 -0
  68. package/build/map/types/offline.types.d.ts +132 -0
  69. package/build/map/types/offline.types.js +5 -0
  70. package/build/map/types/overlays.types.d.ts +137 -24
  71. package/build/map/utils/ErrorHandler.d.ts +110 -0
  72. package/build/map/utils/ErrorHandler.js +421 -0
  73. package/build/map/utils/GeoUtils.d.ts +20 -0
  74. package/build/map/utils/GeoUtils.js +76 -0
  75. package/build/map/utils/OfflineMapManager.d.ts +148 -0
  76. package/build/map/utils/OfflineMapManager.js +217 -0
  77. package/build/map/utils/PermissionUtils.d.ts +91 -0
  78. package/build/map/utils/PermissionUtils.js +255 -0
  79. package/build/map/utils/PlatformDetector.d.ts +102 -0
  80. package/build/map/utils/PlatformDetector.js +186 -0
  81. package/build/types/naviview.types.d.ts +1 -1
  82. package/expo-module.config.json +12 -10
  83. package/ios/ExpoGaodeMapNavigation.podspec +9 -0
  84. package/ios/map/ExpoGaodeMapModule.swift +485 -75
  85. package/ios/map/ExpoGaodeMapOfflineModule.swift +479 -0
  86. package/ios/map/ExpoGaodeMapView.swift +611 -62
  87. package/ios/map/ExpoGaodeMapViewModule.swift +48 -26
  88. package/ios/map/MapPreloadManager.swift +348 -0
  89. package/ios/map/cpp/ClusterEngine.cpp +110 -0
  90. package/ios/map/cpp/ClusterEngine.hpp +20 -0
  91. package/ios/map/cpp/ColorParser.cpp +135 -0
  92. package/ios/map/cpp/ColorParser.hpp +14 -0
  93. package/ios/map/cpp/GeometryEngine.cpp +574 -0
  94. package/ios/map/cpp/GeometryEngine.hpp +159 -0
  95. package/ios/map/cpp/QuadTree.cpp +92 -0
  96. package/ios/map/cpp/QuadTree.hpp +42 -0
  97. package/ios/map/cpp/README.md +55 -0
  98. package/ios/map/cpp/tests/benchmark_js.js +41 -0
  99. package/ios/map/cpp/tests/run.sh +17 -0
  100. package/ios/map/cpp/tests/test_main.cpp +276 -0
  101. package/ios/map/managers/UIManager.swift +72 -1
  102. package/ios/map/modules/LocationManager.swift +114 -165
  103. package/ios/map/overlays/CircleView.swift +16 -32
  104. package/ios/map/overlays/CircleViewModule.swift +12 -12
  105. package/ios/map/overlays/ClusterAnnotation.swift +32 -0
  106. package/ios/map/overlays/ClusterView.swift +331 -45
  107. package/ios/map/overlays/ClusterViewModule.swift +20 -6
  108. package/ios/map/overlays/HeatMapView.swift +135 -32
  109. package/ios/map/overlays/HeatMapViewModule.swift +20 -8
  110. package/ios/map/overlays/MarkerView.swift +613 -130
  111. package/ios/map/overlays/MarkerViewModule.swift +38 -18
  112. package/ios/map/overlays/MultiPointView.swift +168 -10
  113. package/ios/map/overlays/MultiPointViewModule.swift +27 -5
  114. package/ios/map/overlays/PolygonView.swift +62 -23
  115. package/ios/map/overlays/PolygonViewModule.swift +18 -12
  116. package/ios/map/overlays/PolylineView.swift +21 -13
  117. package/ios/map/overlays/PolylineViewModule.swift +18 -12
  118. package/ios/map/utils/ClusterNative.h +96 -0
  119. package/ios/map/utils/ClusterNative.mm +377 -0
  120. package/ios/map/utils/ColorParser.swift +12 -1
  121. package/ios/map/utils/CppBridging.mm +13 -0
  122. package/ios/map/utils/GeometryUtils.swift +34 -0
  123. package/ios/map/utils/LatLngParser.swift +87 -0
  124. package/ios/map/utils/PermissionManager.swift +135 -6
  125. package/package.json +1 -1
  126. package/build/map/ExpoGaodeMap.types.d.ts +0 -41
  127. package/build/map/ExpoGaodeMap.types.js +0 -24
  128. package/build/map/utils/EventManager.d.ts +0 -10
  129. package/build/map/utils/EventManager.js +0 -26
  130. package/build/map/utils/ModuleLoader.d.ts +0 -73
  131. package/build/map/utils/ModuleLoader.js +0 -112
@@ -3,6 +3,7 @@ package expo.modules.gaodemap.map
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
5
  import android.view.View
6
+ import android.view.ViewGroup
6
7
  import com.amap.api.maps.AMap
7
8
  import com.amap.api.maps.MapView
8
9
  import com.amap.api.maps.MapsInitializer
@@ -12,8 +13,10 @@ import expo.modules.kotlin.viewevent.EventDispatcher
12
13
  import expo.modules.kotlin.views.ExpoView
13
14
  import expo.modules.gaodemap.map.managers.CameraManager
14
15
  import expo.modules.gaodemap.map.managers.UIManager
15
- import expo.modules.gaodemap.map.overlays.CircleView
16
16
  import expo.modules.gaodemap.map.overlays.*
17
+ import androidx.core.graphics.createBitmap
18
+ import androidx.core.view.isVisible
19
+ import androidx.core.graphics.withTranslation
17
20
 
18
21
  /**
19
22
  * 高德地图视图组件
@@ -24,6 +27,8 @@ import expo.modules.gaodemap.map.overlays.*
24
27
  * - 相机控制和覆盖物管理
25
28
  * - 生命周期管理
26
29
  */
30
+
31
+ @SuppressLint("ViewConstructor")
27
32
  class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
28
33
 
29
34
  /**
@@ -45,6 +50,8 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
45
50
  internal var initialCameraPosition: Map<String, Any?>? = null
46
51
  /** 是否跟随用户位置 */
47
52
  internal var followUserLocation: Boolean = false
53
+ /** 自定义地图样式配置(缓存) */
54
+ private var customMapStyleData: Map<String, Any>? = null
48
55
 
49
56
  /** 主线程 Handler */
50
57
  private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
@@ -57,6 +64,21 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
57
64
  private val onCameraMove by EventDispatcher()
58
65
  private val onCameraIdle by EventDispatcher()
59
66
 
67
+ // 事件节流控制
68
+ /** 相机移动事件节流间隔(毫秒) */
69
+ private val CAMERA_MOVE_THROTTLE_MS = 100L
70
+ /** 上次触发相机移动事件的时间戳 */
71
+ private var lastCameraMoveTime = 0L
72
+ /** 缓存的相机移动事件数据 */
73
+ private var pendingCameraMoveData: Map<String, Any>? = null
74
+ /** 节流定时器 Runnable */
75
+ private val throttleRunnable = Runnable {
76
+ pendingCameraMoveData?.let { data ->
77
+ onCameraMove(data)
78
+ pendingCameraMoveData = null
79
+ }
80
+ }
81
+
60
82
  // 高德地图视图
61
83
  private lateinit var mapView: MapView
62
84
  private lateinit var aMap: AMap
@@ -75,9 +97,19 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
75
97
  MapsInitializer.updatePrivacyShow(context, true, true)
76
98
  MapsInitializer.updatePrivacyAgree(context, true)
77
99
 
78
- // 创建地图视图
79
- mapView = MapView(context)
80
- mapView.onCreate(null)
100
+ // 尝试从预加载池获取 MapView
101
+ val preloadedMapView = MapPreloadManager.getPreloadedMapView()
102
+
103
+ if (preloadedMapView != null) {
104
+ mapView = preloadedMapView
105
+ android.util.Log.i("ExpoGaodeMapView", "🚀 使用预加载的 MapView 实例")
106
+ } else {
107
+ // 创建地图视图
108
+ mapView = MapView(context)
109
+ mapView.onCreate(null)
110
+ android.util.Log.i("ExpoGaodeMapView", "⚠️ 创建新的 MapView 实例 (未命中预加载池)")
111
+ }
112
+
81
113
  aMap = mapView.map
82
114
 
83
115
  // 初始化管理器
@@ -115,6 +147,11 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
115
147
  pendingCameraPosition = null
116
148
  }
117
149
 
150
+ // 应用缓存的自定义地图样式
151
+ customMapStyleData?.let { styleData ->
152
+ uiManager.setCustomMapStyle(styleData)
153
+ }
154
+
118
155
  onLoad(mapOf("loaded" to true))
119
156
  }
120
157
  } catch (_: Exception) {
@@ -122,6 +159,19 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
122
159
  }
123
160
  }
124
161
 
162
+ // 辅助监听器列表
163
+ private val cameraChangeListeners = mutableListOf<AMap.OnCameraChangeListener>()
164
+
165
+ fun addCameraChangeListener(listener: AMap.OnCameraChangeListener) {
166
+ if (!cameraChangeListeners.contains(listener)) {
167
+ cameraChangeListeners.add(listener)
168
+ }
169
+ }
170
+
171
+ fun removeCameraChangeListener(listener: AMap.OnCameraChangeListener) {
172
+ cameraChangeListeners.remove(listener)
173
+ }
174
+
125
175
  /**
126
176
  * 设置地图事件监听
127
177
  */
@@ -129,10 +179,14 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
129
179
  // 设置相机移动监听器
130
180
  aMap.setOnCameraChangeListener(object : AMap.OnCameraChangeListener {
131
181
  override fun onCameraChange(cameraPosition: com.amap.api.maps.model.CameraPosition?) {
132
- // 相机移动中
182
+ // 通知辅助监听器
183
+ cameraChangeListeners.forEach { it.onCameraChange(cameraPosition) }
184
+
185
+ // 相机移动中 - 应用节流优化
133
186
  cameraPosition?.let {
187
+ val currentTime = System.currentTimeMillis()
134
188
  val visibleRegion = aMap.projection.visibleRegion
135
- onCameraMove(mapOf(
189
+ val eventData = mapOf(
136
190
  "cameraPosition" to mapOf(
137
191
  "target" to mapOf(
138
192
  "latitude" to it.target.latitude,
@@ -152,11 +206,32 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
152
206
  "longitude" to visibleRegion.nearLeft.longitude
153
207
  )
154
208
  )
155
- ))
209
+ )
210
+
211
+ // 节流逻辑:100ms 内只触发一次
212
+ if (currentTime - lastCameraMoveTime >= CAMERA_MOVE_THROTTLE_MS) {
213
+ // 超过节流时间,立即触发事件
214
+ lastCameraMoveTime = currentTime
215
+ onCameraMove(eventData)
216
+ // 清除待处理的事件和定时器
217
+ mainHandler.removeCallbacks(throttleRunnable)
218
+ pendingCameraMoveData = null
219
+ } else {
220
+ // 在节流时间内,缓存事件数据,使用定时器延迟触发
221
+ pendingCameraMoveData = eventData
222
+ mainHandler.removeCallbacks(throttleRunnable)
223
+ mainHandler.postDelayed(
224
+ throttleRunnable,
225
+ CAMERA_MOVE_THROTTLE_MS - (currentTime - lastCameraMoveTime)
226
+ )
227
+ }
156
228
  }
157
229
  }
158
230
 
159
231
  override fun onCameraChangeFinish(cameraPosition: com.amap.api.maps.model.CameraPosition?) {
232
+ // 通知辅助监听器
233
+ cameraChangeListeners.forEach { it.onCameraChangeFinish(cameraPosition) }
234
+
160
235
  // 相机移动完成
161
236
  cameraPosition?.let {
162
237
  val visibleRegion = aMap.projection.visibleRegion
@@ -187,7 +262,13 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
187
262
 
188
263
  // 设置全局 Marker 点击监听器
189
264
  aMap.setOnMarkerClickListener { marker ->
190
- MarkerView.handleMarkerClick(marker)
265
+ if (MarkerView.handleMarkerClick(marker)) {
266
+ return@setOnMarkerClickListener true
267
+ }
268
+ if (ClusterView.handleMarkerClick(marker)) {
269
+ return@setOnMarkerClickListener true
270
+ }
271
+ false
191
272
  }
192
273
 
193
274
  // 设置全局 Marker 拖拽监听器
@@ -205,6 +286,19 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
205
286
  }
206
287
  })
207
288
 
289
+ // 设置全局 MultiPoint 点击监听器
290
+ aMap.setOnMultiPointClickListener { item ->
291
+ for (i in 0 until childCount) {
292
+ val child = getChildAt(i)
293
+ if (child is MultiPointView) {
294
+ if (child.handleMultiPointClick(item)) {
295
+ return@setOnMultiPointClickListener true
296
+ }
297
+ }
298
+ }
299
+ return@setOnMultiPointClickListener false
300
+ }
301
+
208
302
  aMap.setOnMapClickListener { latLng ->
209
303
  // 检查声明式 PolylineView
210
304
  if (checkDeclarativePolylinePress(latLng)) {
@@ -331,6 +425,18 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
331
425
  fun setShowsBuildings(show: Boolean) = uiManager.setShowsBuildings(show)
332
426
  /** 设置是否显示室内地图 */
333
427
  fun setShowsIndoorMap(show: Boolean) = uiManager.setShowsIndoorMap(show)
428
+
429
+ /**
430
+ * 设置自定义地图样式
431
+ * @param styleData 样式配置
432
+ */
433
+ fun setCustomMapStyle(styleData: Map<String, Any>) {
434
+ customMapStyleData = styleData
435
+ // 如果地图已加载,立即应用样式
436
+ if (isMapLoaded) {
437
+ uiManager.setCustomMapStyle(styleData)
438
+ }
439
+ }
334
440
 
335
441
  // ==================== 相机控制方法 ====================
336
442
 
@@ -378,8 +484,106 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
378
484
  return cameraManager.getCameraPosition()
379
485
  }
380
486
 
487
+ /**
488
+ * 截取地图快照
489
+ * @param promise Promise
490
+ */
491
+ fun takeSnapshot(promise: expo.modules.kotlin.Promise) {
492
+ val isSettled = java.util.concurrent.atomic.AtomicBoolean(false)
493
+
494
+ aMap.getMapScreenShot(object : AMap.OnMapScreenShotListener {
495
+ override fun onMapScreenShot(bitmap: android.graphics.Bitmap?) {
496
+ // 如果已经处理过,直接返回
497
+ if (isSettled.getAndSet(true)) return
498
+
499
+ // 旧版本回调,为了兼容性也处理
500
+ bitmap?.let { handleSnapshot(it, promise) } ?: run {
501
+ promise.reject("SNAPSHOT_FAILED", "Bitmap is null", null)
502
+ }
503
+ }
381
504
 
382
- // ==================== 生命周期方法 ====================
505
+ override fun onMapScreenShot(bitmap: android.graphics.Bitmap?, status: Int) {
506
+ // 如果已经处理过,直接返回
507
+ if (isSettled.getAndSet(true)) return
508
+
509
+ // status != 0 表示失败
510
+ if (status != 0) {
511
+ promise.reject("SNAPSHOT_FAILED", "Failed to take snapshot, status code: $status", null)
512
+ return
513
+ }
514
+ bitmap?.let { handleSnapshot(it, promise) } ?: run {
515
+ promise.reject("SNAPSHOT_FAILED", "Bitmap is null", null)
516
+ }
517
+ }
518
+ })
519
+ }
520
+
521
+ @SuppressLint("WrongThread")
522
+ private fun handleSnapshot(mapBitmap: android.graphics.Bitmap, promise: expo.modules.kotlin.Promise) {
523
+ try {
524
+ // 创建最终的 Bitmap,大小为当前容器的大小
525
+ val width = this.width
526
+ val height = this.height
527
+
528
+ // 如果容器宽高为0,无法截图
529
+ if (width <= 0 || height <= 0) {
530
+ promise.reject("SNAPSHOT_FAILED", "View dimensions are invalid", null)
531
+ return
532
+ }
533
+
534
+ val finalBitmap = createBitmap(width, height)
535
+ val canvas = android.graphics.Canvas(finalBitmap)
536
+
537
+ // 1. 绘制地图底图
538
+ canvas.drawBitmap(mapBitmap, mapView.left.toFloat(), mapView.top.toFloat(), null)
539
+
540
+ // 2. 绘制内部子视图 (React Native Overlays, e.g. Callout)
541
+ for (i in 0 until childCount) {
542
+ val child = getChildAt(i)
543
+ val isMarkerView = child is MarkerView
544
+
545
+ // 跳过地图本身、隐藏的视图以及 MarkerView
546
+ if (child !== mapView && child.isVisible && !isMarkerView) {
547
+ canvas.withTranslation(child.left.toFloat(), child.top.toFloat()) {
548
+ child.draw(this)
549
+ }
550
+ }
551
+ }
552
+
553
+ // 3. 绘制兄弟视图 (MapUI, 覆盖在地图上的 UI 组件)
554
+ // 模仿 iOS 的实现:遍历父容器的子视图,绘制那些覆盖在地图上方的兄弟节点
555
+ (parent as? ViewGroup)?.let { parentGroup ->
556
+ for (i in 0 until parentGroup.childCount) {
557
+ val sibling = parentGroup.getChildAt(i)
558
+ // 跳过自己(地图本身)和隐藏的视图
559
+ if (sibling !== this && sibling.isVisible) {
560
+ // 计算相对坐标:兄弟视图相对于父容器的坐标 - 地图相对于父容器的坐标
561
+ val dx = sibling.left - this.left
562
+ val dy = sibling.top - this.top
563
+
564
+ canvas.withTranslation(dx.toFloat(), dy.toFloat()) {
565
+ sibling.draw(this)
566
+ }
567
+ }
568
+ }
569
+ }
570
+
571
+ // 3. 保存到文件
572
+ val filename = java.util.UUID.randomUUID().toString() + ".png"
573
+ val file = java.io.File(context.cacheDir, filename)
574
+ val stream = java.io.FileOutputStream(file)
575
+ finalBitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, stream)
576
+ stream.close()
577
+
578
+ // 4. 返回文件路径
579
+ promise.resolve(file.absolutePath)
580
+
581
+ } catch (e: Exception) {
582
+ promise.reject("SNAPSHOT_ERROR", "Error processing snapshot: ${e.message}", e)
583
+ }
584
+ }
585
+
586
+ // ==================== 生命周期管理 ====================
383
587
 
384
588
  /** 恢复地图 */
385
589
  @Suppress("unused")
@@ -397,8 +601,12 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
397
601
  @Suppress("unused")
398
602
  fun onDestroy() {
399
603
  try {
400
- // 清理 Handler 回调,防止内存泄露
401
- mainHandler.removeCallbacksAndMessages(null)
604
+ // 清理节流定时器
605
+ mainHandler.removeCallbacks(throttleRunnable)
606
+ pendingCameraMoveData = null
607
+
608
+ // 清理 Handler 回调,防止内存泄露
609
+ mainHandler.removeCallbacksAndMessages(null)
402
610
 
403
611
  // 清理所有地图监听器
404
612
  aMap.setOnMapClickListener(null)
@@ -407,6 +615,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
407
615
  aMap.setOnCameraChangeListener(null)
408
616
  aMap.setOnMarkerClickListener(null)
409
617
  aMap.setOnMarkerDragListener(null)
618
+ aMap.setOnMultiPointClickListener(null)
410
619
 
411
620
  // 清除所有覆盖物
412
621
  aMap.clear()
@@ -466,10 +675,14 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
466
675
 
467
676
  /**
468
677
  * 移除子视图
678
+ * 延迟移除 Marker,让它们跟随地图一起延迟销毁
469
679
  */
470
680
  override fun removeView(child: View?) {
471
681
  if (child is MarkerView) {
472
- child.removeMarker()
682
+ // 延迟移除 Marker,与地图的延迟销毁时间一致(500ms)
683
+ mainHandler.postDelayed({
684
+ child.removeMarker()
685
+ }, 500)
473
686
  super.removeView(child)
474
687
  return
475
688
  }
@@ -483,6 +696,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
483
696
 
484
697
  /**
485
698
  * 按索引移除视图
699
+ * 延迟移除 Marker,让它们跟随地图一起延迟销毁
486
700
  */
487
701
  override fun removeViewAt(index: Int) {
488
702
  try {
@@ -493,7 +707,10 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
493
707
  }
494
708
 
495
709
  if (child is MarkerView) {
496
- child.removeMarker()
710
+ // 延迟移除 Marker,与地图的延迟销毁时间一致(500ms)
711
+ mainHandler.postDelayed({
712
+ child.removeMarker()
713
+ }, 500)
497
714
  }
498
715
 
499
716
  super.removeViewAt(index)
@@ -542,5 +759,4 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
542
759
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
543
760
  super.onLayout(changed, left, top, right, bottom)
544
761
  }
545
-
546
762
  }
@@ -3,22 +3,24 @@ package expo.modules.gaodemap.map
3
3
  import expo.modules.kotlin.modules.Module
4
4
  import expo.modules.kotlin.modules.ModuleDefinition
5
5
 
6
-
7
6
  /**
8
7
  * 高德地图视图 Module
9
8
  */
10
9
  class ExpoGaodeMapViewModule : Module() {
11
10
  override fun definition() = ModuleDefinition {
12
- Name("NaviMapView")
11
+ Name("ExpoGaodeMapView")
13
12
 
14
13
  View(ExpoGaodeMapView::class) {
15
14
  Events("onMapPress", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
16
-
17
- // ✅ 关键修复:拦截 React Native 的视图操作异常
18
-
19
- OnViewDestroys { view ->
20
- // 销毁地图实例,释放资源
21
- view.onDestroy()
15
+
16
+
17
+
18
+ // 延迟销毁地图,避免页面退出动画未完成时地图就变成白屏
19
+ OnViewDestroys { view: ExpoGaodeMapView ->
20
+ // 延迟 500ms 销毁地图,让页面退出动画先完成
21
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
22
+ view.onDestroy()
23
+ }, 500)
22
24
  }
23
25
 
24
26
 
@@ -40,7 +42,7 @@ class ExpoGaodeMapViewModule : Module() {
40
42
  Prop<Boolean>("scrollGesturesEnabled") { view, enabled -> view.setScrollEnabled(enabled) }
41
43
  Prop<Boolean>("rotateGesturesEnabled") { view, enabled -> view.setRotateEnabled(enabled) }
42
44
  Prop<Boolean>("tiltGesturesEnabled") { view, enabled -> view.setTiltEnabled(enabled) }
43
-
45
+
44
46
  Prop<Float>("maxZoom") { view, maxZoom -> view.setMaxZoom(maxZoom) }
45
47
  Prop<Float>("minZoom") { view, minZoom -> view.setMinZoom(minZoom) }
46
48
 
@@ -52,36 +54,44 @@ class ExpoGaodeMapViewModule : Module() {
52
54
  Prop<Boolean>("trafficEnabled") { view, show -> view.setShowsTraffic(show) }
53
55
  Prop<Boolean>("buildingsEnabled") { view, show -> view.setShowsBuildings(show) }
54
56
  Prop<Boolean>("indoorViewEnabled") { view, show -> view.setShowsIndoorMap(show) }
57
+
58
+ Prop<Map<String, Any>?>("customMapStyle") { view, styleData ->
59
+ styleData?.let { view.setCustomMapStyle(it) }
60
+ }
55
61
 
56
- OnViewDidUpdateProps { view ->
62
+ OnViewDidUpdateProps { view: ExpoGaodeMapView ->
57
63
  if (view.mapType != 0) {
58
64
  view.setMapType(view.mapType)
59
65
  }
60
-
66
+
61
67
  view.initialCameraPosition?.let { position ->
62
68
  view.setInitialCameraPosition(position)
63
69
  }
64
70
  }
65
71
 
66
- AsyncFunction("moveCamera") { view: ExpoGaodeMapView, position: Map<String, Any>, duration: Int ->
67
- view.moveCamera(position, duration)
68
- }
72
+ AsyncFunction("moveCamera") { view: ExpoGaodeMapView, position: Map<String, Any>, duration: Int ->
73
+ view.moveCamera(position, duration)
74
+ }
69
75
 
70
- AsyncFunction("getLatLng") { view: ExpoGaodeMapView, point: Map<String, Double> ->
71
- view.getLatLng(point)
72
- }
76
+ AsyncFunction("getLatLng") { view: ExpoGaodeMapView, point: Map<String, Double> ->
77
+ view.getLatLng(point)
78
+ }
73
79
 
74
- AsyncFunction("setCenter") { view: ExpoGaodeMapView, center: Map<String, Double>, animated: Boolean ->
75
- view.setCenter(center, animated)
76
- }
80
+ AsyncFunction("takeSnapshot") { view: ExpoGaodeMapView, promise: expo.modules.kotlin.Promise ->
81
+ view.takeSnapshot(promise)
82
+ }
83
+
84
+ AsyncFunction("setCenter") { view: ExpoGaodeMapView, center: Map<String, Double>, animated: Boolean ->
85
+ view.setCenter(center, animated)
86
+ }
77
87
 
78
- AsyncFunction("setZoom") { view: ExpoGaodeMapView, zoom: Double, animated: Boolean ->
79
- view.setZoomLevel(zoom.toFloat(), animated)
80
- }
88
+ AsyncFunction("setZoom") { view: ExpoGaodeMapView, zoom: Double, animated: Boolean ->
89
+ view.setZoomLevel(zoom.toFloat(), animated)
90
+ }
81
91
 
82
- AsyncFunction("getCameraPosition") { view: ExpoGaodeMapView ->
83
- view.getCameraPosition()
84
- }
92
+ AsyncFunction("getCameraPosition") { view: ExpoGaodeMapView ->
93
+ view.getCameraPosition()
94
+ }
85
95
  }
86
96
  }
87
- }
97
+ }