expo-gaode-map 2.2.29 → 2.2.31-next.0

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 (59) hide show
  1. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapModule.kt +4 -2
  2. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +180 -61
  3. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +4 -1
  4. package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +15 -1
  5. package/android/src/main/java/expo/modules/gaodemap/overlays/ClusterView.kt +24 -13
  6. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerBitmapRenderer.kt +323 -0
  7. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +236 -216
  8. package/build/ExpoGaodeMapModule.d.ts +13 -5
  9. package/build/ExpoGaodeMapModule.d.ts.map +1 -1
  10. package/build/ExpoGaodeMapModule.js +179 -11
  11. package/build/ExpoGaodeMapModule.js.map +1 -1
  12. package/build/ExpoGaodeMapView.d.ts.map +1 -1
  13. package/build/ExpoGaodeMapView.js +36 -18
  14. package/build/ExpoGaodeMapView.js.map +1 -1
  15. package/build/components/FoldableMapView.d.ts.map +1 -1
  16. package/build/components/FoldableMapView.js +115 -104
  17. package/build/components/FoldableMapView.js.map +1 -1
  18. package/build/components/overlays/Cluster.d.ts.map +1 -1
  19. package/build/components/overlays/Cluster.js +12 -0
  20. package/build/components/overlays/Cluster.js.map +1 -1
  21. package/build/components/overlays/Marker.d.ts.map +1 -1
  22. package/build/components/overlays/Marker.js +67 -4
  23. package/build/components/overlays/Marker.js.map +1 -1
  24. package/build/index.d.ts +0 -1
  25. package/build/index.d.ts.map +1 -1
  26. package/build/index.js +0 -2
  27. package/build/index.js.map +1 -1
  28. package/build/types/common.types.d.ts +24 -0
  29. package/build/types/common.types.d.ts.map +1 -1
  30. package/build/types/common.types.js.map +1 -1
  31. package/build/types/index.d.ts +2 -2
  32. package/build/types/index.d.ts.map +1 -1
  33. package/build/types/index.js.map +1 -1
  34. package/build/types/location.types.d.ts +28 -0
  35. package/build/types/location.types.d.ts.map +1 -1
  36. package/build/types/location.types.js +5 -0
  37. package/build/types/location.types.js.map +1 -1
  38. package/build/types/map-view.types.d.ts +16 -22
  39. package/build/types/map-view.types.d.ts.map +1 -1
  40. package/build/types/map-view.types.js.map +1 -1
  41. package/build/types/native-module.types.d.ts +2 -2
  42. package/build/types/native-module.types.d.ts.map +1 -1
  43. package/build/types/native-module.types.js.map +1 -1
  44. package/build/types/overlays.types.d.ts +11 -2
  45. package/build/types/overlays.types.d.ts.map +1 -1
  46. package/build/types/overlays.types.js.map +1 -1
  47. package/ios/ExpoGaodeMapModule.swift +41 -22
  48. package/ios/ExpoGaodeMapView.swift +226 -234
  49. package/ios/ExpoGaodeMapViewModule.swift +14 -1
  50. package/ios/modules/LocationManager.swift +32 -9
  51. package/ios/overlays/ClusterView.swift +114 -12
  52. package/ios/overlays/ClusterViewModule.swift +5 -1
  53. package/ios/overlays/MarkerView.swift +324 -68
  54. package/ios/overlays/MarkerViewModule.swift +6 -6
  55. package/package.json +6 -6
  56. package/build/utils/throttle.d.ts +0 -10
  57. package/build/utils/throttle.d.ts.map +0 -1
  58. package/build/utils/throttle.js +0 -19
  59. package/build/utils/throttle.js.map +0 -1
@@ -772,7 +772,7 @@ class ExpoGaodeMapModule : Module() {
772
772
  // 使用 WeakReference 避免内存泄露
773
773
  val contextRef = java.lang.ref.WeakReference(appContext.reactContext)
774
774
  val handler = android.os.Handler(android.os.Looper.getMainLooper())
775
- val attempts = 0
775
+ var attempts = 0
776
776
  val maxAttempts = 50 // 增加到 5 秒 / 100ms,给用户足够时间操作
777
777
 
778
778
  val checkPermission = object : Runnable {
@@ -784,6 +784,7 @@ class ExpoGaodeMapModule : Module() {
784
784
  }
785
785
 
786
786
  val status = PermissionHelper.checkForegroundLocationPermission(context)
787
+ attempts += 1
787
788
 
788
789
  // 如果权限已授予或达到最大尝试次数,返回结果并清理 Handler
789
790
  if (status.granted || attempts >= maxAttempts) {
@@ -843,7 +844,7 @@ class ExpoGaodeMapModule : Module() {
843
844
  // 轮询检查权限状态
844
845
  val contextRef = java.lang.ref.WeakReference(appContext.reactContext)
845
846
  val handler = android.os.Handler(android.os.Looper.getMainLooper())
846
- val attempts = 0
847
+ var attempts = 0
847
848
  val maxAttempts = 30
848
849
 
849
850
  val checkPermission = object : Runnable {
@@ -855,6 +856,7 @@ class ExpoGaodeMapModule : Module() {
855
856
  }
856
857
 
857
858
  val status = PermissionHelper.checkBackgroundLocationPermission(context)
859
+ attempts += 1
858
860
 
859
861
  if (status.granted || attempts >= maxAttempts) {
860
862
  handler.removeCallbacks(this)
@@ -2,6 +2,7 @@ package expo.modules.gaodemap
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.os.SystemClock
5
6
  import android.view.View
6
7
  import android.view.ViewGroup
7
8
  import com.amap.api.maps.AMap
@@ -32,6 +33,10 @@ import androidx.core.graphics.withTranslation
32
33
  @SuppressLint("ViewConstructor")
33
34
  class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
34
35
 
36
+ init {
37
+ orientation = VERTICAL
38
+ }
39
+
35
40
  /**
36
41
  * 拦截 React Native 的 ViewManager 操作
37
42
  * 重写 requestLayout 防止在移除视图时触发布局异常
@@ -44,6 +49,63 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
44
49
  }
45
50
  }
46
51
 
52
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
53
+ val measuredWidth = View.MeasureSpec.getSize(widthMeasureSpec)
54
+ val measuredHeight = View.MeasureSpec.getSize(heightMeasureSpec)
55
+
56
+ setMeasuredDimension(measuredWidth, measuredHeight)
57
+
58
+ for (i in 0 until childCount) {
59
+ val child = getChildAt(i) ?: continue
60
+ if (child.visibility == View.GONE) {
61
+ continue
62
+ }
63
+
64
+ if (child === mapView) {
65
+ val childWidthSpec = View.MeasureSpec.makeMeasureSpec(measuredWidth, View.MeasureSpec.EXACTLY)
66
+ val childHeightSpec = View.MeasureSpec.makeMeasureSpec(measuredHeight, View.MeasureSpec.EXACTLY)
67
+ child.measure(childWidthSpec, childHeightSpec)
68
+ continue
69
+ }
70
+
71
+ val lp = child.layoutParams
72
+ val childWidthSpec = when {
73
+ lp?.width != null && lp.width > 0 ->
74
+ View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY)
75
+ else ->
76
+ View.MeasureSpec.makeMeasureSpec(measuredWidth, View.MeasureSpec.AT_MOST)
77
+ }
78
+ val childHeightSpec = when {
79
+ lp?.height != null && lp.height > 0 ->
80
+ View.MeasureSpec.makeMeasureSpec(lp.height, View.MeasureSpec.EXACTLY)
81
+ else ->
82
+ View.MeasureSpec.makeMeasureSpec(measuredHeight, View.MeasureSpec.AT_MOST)
83
+ }
84
+ child.measure(childWidthSpec, childHeightSpec)
85
+ }
86
+ }
87
+
88
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
89
+ val width = right - left
90
+ val height = bottom - top
91
+
92
+ for (i in 0 until childCount) {
93
+ val child = getChildAt(i) ?: continue
94
+ if (child.visibility == View.GONE) {
95
+ continue
96
+ }
97
+
98
+ if (child === mapView) {
99
+ child.layout(0, 0, width, height)
100
+ continue
101
+ }
102
+
103
+ val childWidth = child.measuredWidth
104
+ val childHeight = child.measuredHeight
105
+ child.layout(0, 0, childWidth, childHeight)
106
+ }
107
+ }
108
+
47
109
  // Props 存储
48
110
  /** 地图类型 */
49
111
  internal var mapType: Int = 0
@@ -51,6 +113,12 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
51
113
  internal var initialCameraPosition: Map<String, Any?>? = null
52
114
  /** 是否跟随用户位置 */
53
115
  internal var followUserLocation: Boolean = false
116
+ /** 是否显示底图文字标注 */
117
+ private var labelsEnabled: Boolean = true
118
+ /** 是否显示定位按钮 */
119
+ private var myLocationButtonEnabled: Boolean = false
120
+ /** 相机移动事件节流间隔 */
121
+ private var cameraEventThrottleMs: Long = 32L
54
122
  /** 自定义地图样式配置(缓存) */
55
123
  private var customMapStyleData: Map<String, Any>? = null
56
124
 
@@ -59,6 +127,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
59
127
 
60
128
  // 事件派发器
61
129
  private val onMapPress by EventDispatcher()
130
+ private val onPressPoi by EventDispatcher()
62
131
  private val onMapLongPress by EventDispatcher()
63
132
  private val onLoad by EventDispatcher()
64
133
  private val onLocation by EventDispatcher()
@@ -67,6 +136,8 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
67
136
 
68
137
  // 缓存的相机移动事件数据
69
138
  private var pendingCameraMoveData: Map<String, Any>? = null
139
+ private var pendingCameraMoveDispatch: Runnable? = null
140
+ private var lastCameraMoveDispatchAt: Long = 0L
70
141
 
71
142
  // 高德地图视图
72
143
  private lateinit var mapView: TextureMapView
@@ -130,6 +201,9 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
130
201
  uiManager.setCustomMapStyle(styleData)
131
202
  }
132
203
 
204
+ uiManager.setLabelsEnabled(labelsEnabled)
205
+ uiManager.setMyLocationButtonEnabled(myLocationButtonEnabled)
206
+
133
207
  onLoad(mapOf("loaded" to true))
134
208
  }
135
209
  } catch (_: Exception) {
@@ -162,39 +236,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
162
236
 
163
237
  // 相机移动中 - 应用节流优化
164
238
  cameraPosition?.let {
165
- val visibleRegion = aMap.projection.visibleRegion
166
- val eventData = mapOf(
167
- "cameraPosition" to mapOf(
168
- "target" to mapOf(
169
- "latitude" to it.target.latitude,
170
- "longitude" to it.target.longitude
171
- ),
172
- "zoom" to it.zoom,
173
- "tilt" to it.tilt,
174
- "bearing" to it.bearing
175
- ),
176
- "latLngBounds" to mapOf(
177
- "northeast" to mapOf(
178
- "latitude" to visibleRegion.farRight.latitude,
179
- "longitude" to visibleRegion.farRight.longitude
180
- ),
181
- "southwest" to mapOf(
182
- "latitude" to visibleRegion.nearLeft.latitude,
183
- "longitude" to visibleRegion.nearLeft.longitude
184
- )
185
- )
186
- )
187
-
188
- // 使用 onCameraMove 自身的节流机制(如果在 Module 定义中配置了 Coalescing)
189
- // 或者在这里简单发送,让 JS 端处理节流,或者依赖 Expo 的事件批处理
190
- // 这里我们移除自定义的 Handler 实现,直接发送事件,简化代码逻辑
191
- // 注意:高德地图的 onCameraChange 调用频率非常高,
192
- // 建议在 Module 定义中使用 Events("onCameraMove") 时考虑是否需要原生侧节流
193
- // 目前 Expo Modules 默认没有自动节流,但为了代码简洁和避免 Handler 泄漏风险,
194
- // 我们可以依赖 JS 端的 debounce/throttle,或者如果性能是瓶颈,再加回轻量级的节流。
195
- // 鉴于之前的 Handler 实现比较复杂且容易出错,我们先简化。
196
-
197
- onCameraMove(eventData)
239
+ dispatchCameraMoveEvent(buildCameraEventData(it))
198
240
  }
199
241
  }
200
242
 
@@ -204,28 +246,8 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
204
246
 
205
247
  // 相机移动完成
206
248
  cameraPosition?.let {
207
- val visibleRegion = aMap.projection.visibleRegion
208
- onCameraIdle(mapOf(
209
- "cameraPosition" to mapOf(
210
- "target" to mapOf(
211
- "latitude" to it.target.latitude,
212
- "longitude" to it.target.longitude
213
- ),
214
- "zoom" to it.zoom,
215
- "tilt" to it.tilt,
216
- "bearing" to it.bearing
217
- ),
218
- "latLngBounds" to mapOf(
219
- "northeast" to mapOf(
220
- "latitude" to visibleRegion.farRight.latitude,
221
- "longitude" to visibleRegion.farRight.longitude
222
- ),
223
- "southwest" to mapOf(
224
- "latitude" to visibleRegion.nearLeft.latitude,
225
- "longitude" to visibleRegion.nearLeft.longitude
226
- )
227
- )
228
- ))
249
+ flushPendingCameraMoveEvent()
250
+ onCameraIdle(buildCameraEventData(it))
229
251
  }
230
252
  }
231
253
  })
@@ -292,6 +314,17 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
292
314
  ))
293
315
  }
294
316
 
317
+ aMap.setOnPOIClickListener { poi ->
318
+ onPressPoi(mapOf(
319
+ "id" to poi.poiId,
320
+ "name" to poi.name,
321
+ "position" to mapOf(
322
+ "latitude" to poi.coordinate.latitude,
323
+ "longitude" to poi.coordinate.longitude
324
+ )
325
+ ))
326
+ }
327
+
295
328
  aMap.setOnMapLongClickListener { latLng ->
296
329
  onMapLongPress(mapOf(
297
330
  "latitude" to latLng.latitude,
@@ -395,6 +428,24 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
395
428
  fun setShowsBuildings(show: Boolean) = uiManager.setShowsBuildings(show)
396
429
  /** 设置是否显示室内地图 */
397
430
  fun setShowsIndoorMap(show: Boolean) = uiManager.setShowsIndoorMap(show)
431
+ /** 设置是否显示底图文字标注 */
432
+ fun setLabelsEnabled(enabled: Boolean) {
433
+ labelsEnabled = enabled
434
+ uiManager.setLabelsEnabled(enabled)
435
+ }
436
+ /** 设置是否显示定位按钮 */
437
+ fun setMyLocationButtonEnabled(enabled: Boolean) {
438
+ myLocationButtonEnabled = enabled
439
+ uiManager.setMyLocationButtonEnabled(enabled)
440
+ }
441
+ /** 设置相机移动事件节流间隔 */
442
+ fun setCameraEventThrottleMs(throttleMs: Int) {
443
+ cameraEventThrottleMs = throttleMs.toLong().coerceAtLeast(0L)
444
+ if (cameraEventThrottleMs == 0L) {
445
+ pendingCameraMoveDispatch?.let(mainHandler::removeCallbacks)
446
+ pendingCameraMoveDispatch = null
447
+ }
448
+ }
398
449
 
399
450
  /**
400
451
  * 设置自定义地图样式
@@ -580,9 +631,12 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
580
631
  try {
581
632
  // 清理 Handler 回调,防止内存泄露
582
633
  mainHandler.removeCallbacksAndMessages(null)
634
+ pendingCameraMoveData = null
635
+ pendingCameraMoveDispatch = null
583
636
 
584
637
  // 清理所有地图监听器
585
638
  aMap.setOnMapClickListener(null)
639
+ aMap.setOnPOIClickListener(null)
586
640
  aMap.setOnMapLongClickListener(null)
587
641
  aMap.setOnMapLoadedListener(null)
588
642
  aMap.setOnCameraChangeListener(null)
@@ -602,6 +656,73 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
602
656
 
603
657
  }
604
658
 
659
+ private fun buildCameraEventData(cameraPosition: com.amap.api.maps.model.CameraPosition): Map<String, Any> {
660
+ val visibleRegion = aMap.projection.visibleRegion
661
+ return mapOf(
662
+ "cameraPosition" to mapOf(
663
+ "target" to mapOf(
664
+ "latitude" to cameraPosition.target.latitude,
665
+ "longitude" to cameraPosition.target.longitude
666
+ ),
667
+ "zoom" to cameraPosition.zoom,
668
+ "tilt" to cameraPosition.tilt,
669
+ "bearing" to cameraPosition.bearing
670
+ ),
671
+ "latLngBounds" to mapOf(
672
+ "northeast" to mapOf(
673
+ "latitude" to visibleRegion.farRight.latitude,
674
+ "longitude" to visibleRegion.farRight.longitude
675
+ ),
676
+ "southwest" to mapOf(
677
+ "latitude" to visibleRegion.nearLeft.latitude,
678
+ "longitude" to visibleRegion.nearLeft.longitude
679
+ )
680
+ )
681
+ )
682
+ }
683
+
684
+ private fun dispatchCameraMoveEvent(eventData: Map<String, Any>) {
685
+ val throttleMs = cameraEventThrottleMs.coerceAtLeast(0L)
686
+ if (throttleMs == 0L) {
687
+ pendingCameraMoveData = null
688
+ pendingCameraMoveDispatch?.let(mainHandler::removeCallbacks)
689
+ pendingCameraMoveDispatch = null
690
+ lastCameraMoveDispatchAt = SystemClock.uptimeMillis()
691
+ onCameraMove(eventData)
692
+ return
693
+ }
694
+
695
+ pendingCameraMoveData = eventData
696
+
697
+ val now = SystemClock.uptimeMillis()
698
+ val elapsed = now - lastCameraMoveDispatchAt
699
+ if (elapsed >= throttleMs) {
700
+ pendingCameraMoveDispatch?.let(mainHandler::removeCallbacks)
701
+ pendingCameraMoveDispatch = null
702
+ flushPendingCameraMoveEvent(now)
703
+ return
704
+ }
705
+
706
+ if (pendingCameraMoveDispatch != null) {
707
+ return
708
+ }
709
+
710
+ val delay = (throttleMs - elapsed).coerceAtLeast(0L)
711
+ val runnable = Runnable {
712
+ pendingCameraMoveDispatch = null
713
+ flushPendingCameraMoveEvent()
714
+ }
715
+ pendingCameraMoveDispatch = runnable
716
+ mainHandler.postDelayed(runnable, delay)
717
+ }
718
+
719
+ private fun flushPendingCameraMoveEvent(timestamp: Long = SystemClock.uptimeMillis()) {
720
+ val eventData = pendingCameraMoveData ?: return
721
+ pendingCameraMoveData = null
722
+ lastCameraMoveDispatchAt = timestamp
723
+ onCameraMove(eventData)
724
+ }
725
+
605
726
  /** 保存实例状态 */
606
727
  @Suppress("unused")
607
728
  fun onSaveInstanceState(outState: android.os.Bundle) {
@@ -617,9 +738,10 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
617
738
  override fun addView(child: View?, index: Int) {
618
739
  if (child is MarkerView) {
619
740
  child.setMap(aMap)
620
- // MarkerView 也加入实际视图层级,但设置为 0x0 大小并移到屏幕外
621
- // 不使用 View.GONE,避免在新架构下出现渲染问题
622
- val params = LayoutParams(0, 0)
741
+ // MarkerView 需要保留可测量尺寸,否则 Android 无法正确处理
742
+ // Text / View 的 maxWidth 等布局约束,最终会被测成整行宽度。
743
+ // 这里保留 WRAP_CONTENT,并继续移到屏幕外,避免影响可见布局。
744
+ val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
623
745
  child.layoutParams = params
624
746
  child.translationX = -10000f // 移到屏幕外
625
747
  child.translationY = -10000f
@@ -729,7 +851,4 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
729
851
  return false
730
852
  }
731
853
 
732
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
733
- super.onLayout(changed, left, top, right, bottom)
734
- }
735
854
  }
@@ -21,7 +21,7 @@ class ExpoGaodeMapViewModule : Module() {
21
21
  Name("ExpoGaodeMapView")
22
22
 
23
23
  View(ExpoGaodeMapView::class) {
24
- Events("onMapPress", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
24
+ Events("onMapPress", "onPressPoi", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
25
25
 
26
26
 
27
27
 
@@ -64,6 +64,9 @@ class ExpoGaodeMapViewModule : Module() {
64
64
  Prop<Boolean>("trafficEnabled") { view, show -> view.setShowsTraffic(show) }
65
65
  Prop<Boolean>("buildingsEnabled") { view, show -> view.setShowsBuildings(show) }
66
66
  Prop<Boolean>("indoorViewEnabled") { view, show -> view.setShowsIndoorMap(show) }
67
+ Prop<Boolean>("labelsEnabled") { view, enabled -> view.setLabelsEnabled(enabled) }
68
+ Prop<Boolean>("myLocationButtonEnabled") { view, enabled -> view.setMyLocationButtonEnabled(enabled) }
69
+ Prop<Int>("cameraEventThrottleMs") { view, throttleMs -> view.setCameraEventThrottleMs(throttleMs) }
67
70
 
68
71
  Prop<Map<String, Any>?>("customMapStyle") { view, styleData ->
69
72
  styleData?.let { view.setCustomMapStyle(it) }
@@ -46,6 +46,13 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
46
46
  fun setShowsScale(show: Boolean) {
47
47
  aMap.uiSettings.isScaleControlsEnabled = show
48
48
  }
49
+
50
+ /**
51
+ * 设置是否显示定位按钮
52
+ */
53
+ fun setMyLocationButtonEnabled(enabled: Boolean) {
54
+ aMap.uiSettings.isMyLocationButtonEnabled = enabled
55
+ }
49
56
 
50
57
  // ==================== 手势控制 ====================
51
58
 
@@ -322,7 +329,7 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
322
329
  fun setShowsTraffic(show: Boolean) {
323
330
  aMap.isTrafficEnabled = show
324
331
  }
325
-
332
+
326
333
  /**
327
334
  * 设置是否显示建筑物
328
335
  */
@@ -336,6 +343,13 @@ class UIManager(private val aMap: AMap, private val context: Context) : Location
336
343
  fun setShowsIndoorMap(show: Boolean) {
337
344
  aMap.showIndoorMap(show)
338
345
  }
346
+
347
+ /**
348
+ * 设置是否显示底图文字标注
349
+ */
350
+ fun setLabelsEnabled(enabled: Boolean) {
351
+ aMap.showMapText(enabled)
352
+ }
339
353
 
340
354
  /**
341
355
  * 设置地图类型
@@ -2,7 +2,6 @@ package expo.modules.gaodemap.overlays
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
- import android.util.Log
6
5
  import android.graphics.Bitmap
7
6
  import android.graphics.BitmapFactory
8
7
  import android.graphics.Canvas
@@ -64,7 +63,6 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
64
63
 
65
64
  }
66
65
 
67
- private var rawPoints: List<Map<String, Any>> = emptyList()
68
66
  private var clusterItems: List<ClusterItem> = emptyList()
69
67
  private var clusters: List<Cluster> = emptyList()
70
68
 
@@ -91,6 +89,7 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
91
89
  private var currentIconDescriptor: BitmapDescriptor? = null
92
90
  private var customIconBitmap: Bitmap? = null
93
91
  private var pendingIconUri: String? = null
92
+ private var pendingRetryUpdate: Runnable? = null
94
93
 
95
94
  // 标记样式是否发生变化,用于强制更新图标
96
95
  private var styleChanged = false
@@ -117,7 +116,6 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
117
116
  * 设置聚合点
118
117
  */
119
118
  fun setPoints(points: List<Map<String, Any>>) {
120
- rawPoints = points
121
119
  clusterItems = points.mapNotNull { pointData ->
122
120
  LatLngParser.parseLatLng(pointData)?.let { latLng ->
123
121
  ClusterItem(latLng, pointData)
@@ -141,7 +139,6 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
141
139
  * 设置最小聚合数量
142
140
  */
143
141
  fun setMinClusterSize(size: Int) {
144
- Log.d("ClusterView", "setMinClusterSize: $size")
145
142
  minClusterSize = size
146
143
  updateClusters()
147
144
  }
@@ -227,8 +224,8 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
227
224
  updateClusters()
228
225
  }
229
226
  }
230
- } catch (e: Exception) {
231
- e.printStackTrace()
227
+ } catch (_: Exception) {
228
+ // 忽略异常,保留当前默认图标
232
229
  }
233
230
  }
234
231
  }
@@ -265,6 +262,8 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
265
262
  }
266
263
  currentMarkers.clear()
267
264
  bitmapCache.clear()
265
+ pendingRetryUpdate?.let { mainHandler.removeCallbacks(it) }
266
+ pendingRetryUpdate = null
268
267
  }
269
268
 
270
269
  /**
@@ -283,7 +282,12 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
283
282
  }
284
283
 
285
284
  calculationJob = scope.launch(Dispatchers.Default) {
286
- if (clusterItems.isEmpty()) return@launch
285
+ if (clusterItems.isEmpty()) {
286
+ withContext(Dispatchers.Main) {
287
+ renderClusters(emptyList())
288
+ }
289
+ return@launch
290
+ }
287
291
 
288
292
  // 获取当前比例尺 (米/像素)
289
293
  val scalePerPixel = withContext(Dispatchers.Main) {
@@ -296,11 +300,7 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
296
300
  }
297
301
 
298
302
  if (scalePerPixel <= 0) {
299
- // 比例尺无效,稍后重试
300
- withContext(Dispatchers.Main) {
301
- Log.w("ClusterView", "Invalid scalePerPixel: $scalePerPixel, retrying...")
302
- mainHandler.postDelayed({ updateClusters() }, 500)
303
- }
303
+ scheduleRetryUpdate()
304
304
  return@launch
305
305
  }
306
306
 
@@ -319,6 +319,18 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
319
319
  }
320
320
  }
321
321
 
322
+ private fun scheduleRetryUpdate(delayMs: Long = 500L) {
323
+ pendingRetryUpdate?.let { mainHandler.removeCallbacks(it) }
324
+ val retryTask = Runnable {
325
+ pendingRetryUpdate = null
326
+ if (aMap != null) {
327
+ updateClusters()
328
+ }
329
+ }
330
+ pendingRetryUpdate = retryTask
331
+ mainHandler.postDelayed(retryTask, delayMs)
332
+ }
333
+
322
334
  private fun buildClustersFromNative(radiusMeters: Double): List<Cluster>? {
323
335
  return try {
324
336
  val latitudes = DoubleArray(clusterItems.size)
@@ -396,7 +408,6 @@ class ClusterView(context: Context, appContext: AppContext) : ExpoView(context,
396
408
  * 使用 Diff 算法优化渲染,避免全量刷新导致的闪烁
397
409
  */
398
410
  private fun renderClusters(newClusters: List<Cluster>) {
399
- Log.d("ClusterView", "renderClusters: count=${newClusters.size}, minClusterSize=$minClusterSize")
400
411
  val map = aMap ?: return
401
412
  clusters = newClusters
402
413