expo-gaode-map 1.1.8 → 2.0.0-alpha.1

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 (115) hide show
  1. package/README.en.md +32 -46
  2. package/README.md +50 -70
  3. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +37 -268
  4. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +1 -49
  5. package/android/src/main/java/expo/modules/gaodemap/managers/CameraManager.kt +30 -7
  6. package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +1 -0
  7. package/android/src/main/java/expo/modules/gaodemap/modules/LocationManager.kt +10 -1
  8. package/android/src/main/java/expo/modules/gaodemap/overlays/CircleView.kt +38 -14
  9. package/android/src/main/java/expo/modules/gaodemap/overlays/CircleViewModule.kt +3 -3
  10. package/android/src/main/java/expo/modules/gaodemap/overlays/ClusterView.kt +8 -1
  11. package/android/src/main/java/expo/modules/gaodemap/overlays/HeatMapView.kt +4 -1
  12. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +322 -93
  13. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerViewModule.kt +11 -3
  14. package/android/src/main/java/expo/modules/gaodemap/overlays/MultiPointView.kt +4 -1
  15. package/android/src/main/java/expo/modules/gaodemap/overlays/PolygonView.kt +25 -11
  16. package/android/src/main/java/expo/modules/gaodemap/overlays/PolygonViewModule.kt +3 -3
  17. package/android/src/main/java/expo/modules/gaodemap/overlays/PolylineView.kt +20 -10
  18. package/android/src/main/java/expo/modules/gaodemap/overlays/PolylineViewModule.kt +6 -2
  19. package/build/ExpoGaodeMap.types.d.ts +27 -6
  20. package/build/ExpoGaodeMap.types.d.ts.map +1 -1
  21. package/build/ExpoGaodeMap.types.js +3 -0
  22. package/build/ExpoGaodeMap.types.js.map +1 -1
  23. package/build/ExpoGaodeMapModule.d.ts +157 -10
  24. package/build/ExpoGaodeMapModule.d.ts.map +1 -1
  25. package/build/ExpoGaodeMapModule.js +4 -0
  26. package/build/ExpoGaodeMapModule.js.map +1 -1
  27. package/build/ExpoGaodeMapView.d.ts +1 -17
  28. package/build/ExpoGaodeMapView.d.ts.map +1 -1
  29. package/build/ExpoGaodeMapView.js +4 -221
  30. package/build/ExpoGaodeMapView.js.map +1 -1
  31. package/build/components/overlays/Circle.d.ts +11 -1
  32. package/build/components/overlays/Circle.d.ts.map +1 -1
  33. package/build/components/overlays/Circle.js +12 -58
  34. package/build/components/overlays/Circle.js.map +1 -1
  35. package/build/components/overlays/Cluster.d.ts.map +1 -1
  36. package/build/components/overlays/Cluster.js.map +1 -1
  37. package/build/components/overlays/Marker.d.ts +13 -1
  38. package/build/components/overlays/Marker.d.ts.map +1 -1
  39. package/build/components/overlays/Marker.js +51 -115
  40. package/build/components/overlays/Marker.js.map +1 -1
  41. package/build/components/overlays/Polygon.d.ts +7 -15
  42. package/build/components/overlays/Polygon.d.ts.map +1 -1
  43. package/build/components/overlays/Polygon.js +10 -80
  44. package/build/components/overlays/Polygon.js.map +1 -1
  45. package/build/components/overlays/Polyline.d.ts +7 -14
  46. package/build/components/overlays/Polyline.d.ts.map +1 -1
  47. package/build/components/overlays/Polyline.js +9 -66
  48. package/build/components/overlays/Polyline.js.map +1 -1
  49. package/build/index.d.ts +1 -4
  50. package/build/index.d.ts.map +1 -1
  51. package/build/index.js +2 -10
  52. package/build/index.js.map +1 -1
  53. package/build/types/map-view.types.d.ts +0 -90
  54. package/build/types/map-view.types.d.ts.map +1 -1
  55. package/build/types/map-view.types.js.map +1 -1
  56. package/build/types/overlays.types.d.ts +9 -9
  57. package/build/types/overlays.types.d.ts.map +1 -1
  58. package/build/types/overlays.types.js.map +1 -1
  59. package/docs/API.en.md +1 -21
  60. package/docs/API.md +84 -56
  61. package/docs/EXAMPLES.en.md +0 -48
  62. package/docs/EXAMPLES.md +49 -102
  63. package/docs/INITIALIZATION.md +59 -71
  64. package/docs/MIGRATION.md +423 -0
  65. package/ios/ExpoGaodeMapView.swift +317 -258
  66. package/ios/ExpoGaodeMapViewModule.swift +3 -50
  67. package/ios/managers/CameraManager.swift +23 -2
  68. package/ios/managers/UIManager.swift +10 -5
  69. package/ios/modules/LocationManager.swift +10 -0
  70. package/ios/overlays/CircleView.swift +98 -19
  71. package/ios/overlays/CircleViewModule.swift +21 -0
  72. package/ios/overlays/ClusterView.swift +33 -4
  73. package/ios/overlays/HeatMapView.swift +16 -4
  74. package/ios/overlays/MarkerView.swift +235 -146
  75. package/ios/overlays/MarkerViewModule.swift +7 -3
  76. package/ios/overlays/MultiPointView.swift +30 -1
  77. package/ios/overlays/PolygonView.swift +63 -12
  78. package/ios/overlays/PolygonViewModule.swift +17 -0
  79. package/ios/overlays/PolylineView.swift +95 -25
  80. package/ios/overlays/PolylineViewModule.swift +17 -8
  81. package/ios/utils/PermissionManager.swift +9 -14
  82. package/package.json +4 -3
  83. package/src/ExpoGaodeMap.types.ts +28 -3
  84. package/src/ExpoGaodeMapModule.ts +201 -12
  85. package/src/ExpoGaodeMapView.tsx +9 -234
  86. package/src/components/overlays/Circle.tsx +14 -70
  87. package/src/components/overlays/Cluster.tsx +0 -1
  88. package/src/components/overlays/Marker.tsx +63 -138
  89. package/src/components/overlays/Polygon.tsx +12 -92
  90. package/src/components/overlays/Polyline.tsx +11 -77
  91. package/src/index.ts +4 -29
  92. package/src/types/map-view.types.ts +1 -85
  93. package/src/types/overlays.types.ts +9 -9
  94. package/android/src/main/java/expo/modules/gaodemap/managers/OverlayManager.kt +0 -574
  95. package/build/modules/AMapLocation.d.ts +0 -78
  96. package/build/modules/AMapLocation.d.ts.map +0 -1
  97. package/build/modules/AMapLocation.js +0 -132
  98. package/build/modules/AMapLocation.js.map +0 -1
  99. package/build/modules/AMapPermissions.d.ts +0 -29
  100. package/build/modules/AMapPermissions.d.ts.map +0 -1
  101. package/build/modules/AMapPermissions.js +0 -23
  102. package/build/modules/AMapPermissions.js.map +0 -1
  103. package/build/modules/AMapSDK.d.ts +0 -22
  104. package/build/modules/AMapSDK.d.ts.map +0 -1
  105. package/build/modules/AMapSDK.js +0 -25
  106. package/build/modules/AMapSDK.js.map +0 -1
  107. package/build/modules/AMapView.d.ts +0 -44
  108. package/build/modules/AMapView.d.ts.map +0 -1
  109. package/build/modules/AMapView.js +0 -65
  110. package/build/modules/AMapView.js.map +0 -1
  111. package/ios/managers/OverlayManager.swift +0 -522
  112. package/src/modules/AMapLocation.ts +0 -165
  113. package/src/modules/AMapPermissions.ts +0 -41
  114. package/src/modules/AMapSDK.ts +0 -31
  115. package/src/modules/AMapView.ts +0 -72
@@ -3,6 +3,7 @@ package expo.modules.gaodemap.overlays
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
5
  import android.graphics.Bitmap
6
+ import android.graphics.BitmapFactory
6
7
  import android.graphics.Canvas
7
8
  import android.graphics.Color
8
9
  import android.graphics.Paint
@@ -18,6 +19,11 @@ import com.amap.api.maps.model.MarkerOptions
18
19
  import expo.modules.kotlin.AppContext
19
20
  import expo.modules.kotlin.viewevent.EventDispatcher
20
21
  import expo.modules.kotlin.views.ExpoView
22
+ import java.io.File
23
+ import java.io.InputStream
24
+ import java.net.HttpURLConnection
25
+ import java.net.URL
26
+ import kotlin.concurrent.thread
21
27
 
22
28
  class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
23
29
 
@@ -91,17 +97,16 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
91
97
  try {
92
98
  super.onMeasure(widthMeasureSpec, heightMeasureSpec)
93
99
  } catch (e: Exception) {
94
- android.util.Log.e("MarkerView", "onMeasure 失败", e)
95
100
  throw e
96
101
  }
97
102
  }
98
103
 
99
- private val onPress by EventDispatcher()
100
- private val onDragStart by EventDispatcher()
101
- private val onDrag by EventDispatcher()
102
- private val onDragEnd by EventDispatcher()
104
+ private val onMarkerPress by EventDispatcher()
105
+ private val onMarkerDragStart by EventDispatcher()
106
+ private val onMarkerDrag by EventDispatcher()
107
+ private val onMarkerDragEnd by EventDispatcher()
103
108
 
104
- private var marker: Marker? = null
109
+ internal var marker: Marker? = null
105
110
  private var aMap: AMap? = null
106
111
  private var pendingPosition: LatLng? = null
107
112
  private var pendingLatitude: Double? = null // 临时存储纬度
@@ -111,6 +116,18 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
111
116
  private var customViewWidth: Int = 0 // 用于自定义视图(children)的宽度
112
117
  private var customViewHeight: Int = 0 // 用于自定义视图(children)的高度
113
118
  private val mainHandler = Handler(Looper.getMainLooper())
119
+ private var isRemoving = false // 标记是否正在被移除
120
+
121
+ // 缓存属性,在 marker 创建前保存
122
+ private var pendingTitle: String? = null
123
+ private var pendingSnippet: String? = null
124
+ private var pendingDraggable: Boolean? = null
125
+ private var pendingOpacity: Float? = null
126
+ private var pendingFlat: Boolean? = null
127
+ private var pendingZIndex: Float? = null
128
+ private var pendingAnchor: Pair<Float, Float>? = null
129
+ private var pendingIconUri: String? = null
130
+ private var pendingPinColor: String? = null
114
131
 
115
132
  /**
116
133
  * 设置地图实例
@@ -142,7 +159,6 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
142
159
  fun setLatitude(lat: Double) {
143
160
  try {
144
161
  if (lat < -90 || lat > 90) {
145
- android.util.Log.e("MarkerView", "纬度超出有效范围: $lat")
146
162
  return
147
163
  }
148
164
 
@@ -151,7 +167,7 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
151
167
  updatePosition(lat, lng)
152
168
  }
153
169
  } catch (e: Exception) {
154
- android.util.Log.e("MarkerView", "setLatitude 异常", e)
170
+ // 忽略异常
155
171
  }
156
172
  }
157
173
 
@@ -161,7 +177,6 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
161
177
  fun setLongitude(lng: Double) {
162
178
  try {
163
179
  if (lng < -180 || lng > 180) {
164
- android.util.Log.e("MarkerView", "经度超出有效范围: $lng")
165
180
  return
166
181
  }
167
182
 
@@ -170,7 +185,7 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
170
185
  updatePosition(lat, lng)
171
186
  }
172
187
  } catch (e: Exception) {
173
- android.util.Log.e("MarkerView", "setLongitude 异常", e)
188
+ // 忽略异常
174
189
  }
175
190
  }
176
191
 
@@ -197,7 +212,7 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
197
212
  }
198
213
  }
199
214
  } catch (e: Exception) {
200
- android.util.Log.e("MarkerView", "updatePosition 异常", e)
215
+ // 忽略异常
201
216
  }
202
217
  }
203
218
 
@@ -210,18 +225,16 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
210
225
  val lng = position["longitude"]
211
226
 
212
227
  if (lat == null || lng == null) {
213
- android.util.Log.e("MarkerView", "无效的位置数据")
214
228
  return
215
229
  }
216
230
 
217
231
  if (lat < -90 || lat > 90 || lng < -180 || lng > 180) {
218
- android.util.Log.e("MarkerView", "坐标超出有效范围")
219
232
  return
220
233
  }
221
234
 
222
235
  updatePosition(lat, lng)
223
236
  } catch (e: Exception) {
224
- android.util.Log.e("MarkerView", "setPosition 异常", e)
237
+ // 忽略异常
225
238
  }
226
239
  }
227
240
 
@@ -229,20 +242,35 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
229
242
  * 设置标题
230
243
  */
231
244
  fun setTitle(title: String) {
232
- marker?.let { it.title = title }
245
+ pendingTitle = title
246
+ marker?.let {
247
+ it.title = title
248
+ // 如果信息窗口正在显示,刷新它
249
+ if (it.isInfoWindowShown) {
250
+ it.showInfoWindow()
251
+ }
252
+ }
233
253
  }
234
254
 
235
255
  /**
236
256
  * 设置描述
237
257
  */
238
258
  fun setDescription(description: String) {
239
- marker?.let { it.snippet = description }
259
+ pendingSnippet = description
260
+ marker?.let {
261
+ it.snippet = description
262
+ // 如果信息窗口正在显示,刷新它
263
+ if (it.isInfoWindowShown) {
264
+ it.showInfoWindow()
265
+ }
266
+ }
240
267
  }
241
268
 
242
269
  /**
243
270
  * 设置是否可拖拽
244
271
  */
245
272
  fun setDraggable(draggable: Boolean) {
273
+ pendingDraggable = draggable
246
274
  marker?.let { it.isDraggable = draggable }
247
275
  }
248
276
 
@@ -263,6 +291,7 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
263
291
  * 设置透明度
264
292
  */
265
293
  fun setOpacity(opacity: Float) {
294
+ pendingOpacity = opacity
266
295
  marker?.let { it.alpha = opacity }
267
296
  }
268
297
 
@@ -280,13 +309,15 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
280
309
  fun setAnchor(anchor: Map<String, Float>) {
281
310
  val x = anchor["x"] ?: 0.5f
282
311
  val y = anchor["y"] ?: 1.0f
283
- marker?.setAnchor(x, y)
312
+ pendingAnchor = Pair(x, y)
313
+ marker?.setAnchor(x, y)
284
314
  }
285
315
 
286
316
  /**
287
317
  * 设置是否平贴地图
288
318
  */
289
319
  fun setFlat(flat: Boolean) {
320
+ pendingFlat = flat
290
321
  marker?.let { it.isFlat = flat }
291
322
  }
292
323
 
@@ -294,37 +325,183 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
294
325
  * 设置图标
295
326
  */
296
327
  fun setMarkerIcon(iconUri: String?) {
328
+ pendingIconUri = iconUri
297
329
  iconUri?.let {
298
- // 这里需要根据 URI 加载图片
299
- // 可以支持本地资源、网络图片等
330
+ marker?.let { m ->
331
+ loadAndSetIcon(it, m)
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * 加载并设置图标
338
+ * 支持: http/https 网络图片, file:// 本地文件, 本地资源名
339
+ */
340
+ private fun loadAndSetIcon(iconUri: String, marker: Marker) {
341
+ try {
342
+ when {
343
+ // 网络图片
344
+ iconUri.startsWith("http://") || iconUri.startsWith("https://") -> {
345
+ loadImageFromUrl(iconUri) { bitmap ->
346
+ bitmap?.let {
347
+ val resized = resizeBitmap(it, iconWidth, iconHeight)
348
+ mainHandler.post {
349
+ marker.setIcon(BitmapDescriptorFactory.fromBitmap(resized))
350
+ marker.setAnchor(0.5f, 1.0f)
351
+ }
352
+ } ?: run {
353
+ mainHandler.post {
354
+ marker.setIcon(BitmapDescriptorFactory.defaultMarker())
355
+ }
356
+ }
357
+ }
358
+ }
359
+ // 本地文件
360
+ iconUri.startsWith("file://") -> {
361
+ val path = iconUri.substring(7) // 移除 "file://" 前缀
362
+ val bitmap = BitmapFactory.decodeFile(path)
363
+ if (bitmap != null) {
364
+ val resized = resizeBitmap(bitmap, iconWidth, iconHeight)
365
+ marker.setIcon(BitmapDescriptorFactory.fromBitmap(resized))
366
+ marker.setAnchor(0.5f, 1.0f)
367
+ } else {
368
+ marker.setIcon(BitmapDescriptorFactory.defaultMarker())
369
+ }
370
+ }
371
+ // 本地资源名
372
+ else -> {
373
+ val resourceId = context.resources.getIdentifier(
374
+ iconUri,
375
+ "drawable",
376
+ context.packageName
377
+ )
378
+ if (resourceId != 0) {
379
+ val bitmap = BitmapFactory.decodeResource(context.resources, resourceId)
380
+ val resized = resizeBitmap(bitmap, iconWidth, iconHeight)
381
+ marker.setIcon(BitmapDescriptorFactory.fromBitmap(resized))
382
+ marker.setAnchor(0.5f, 1.0f)
383
+ } else {
384
+ marker.setIcon(BitmapDescriptorFactory.defaultMarker())
385
+ }
386
+ }
387
+ }
388
+ } catch (e: Exception) {
389
+ marker.setIcon(BitmapDescriptorFactory.defaultMarker())
390
+ }
391
+ }
392
+
393
+ /**
394
+ * 从网络加载图片
395
+ */
396
+ private fun loadImageFromUrl(url: String, callback: (Bitmap?) -> Unit) {
397
+ thread {
398
+ var connection: HttpURLConnection? = null
399
+ var inputStream: InputStream? = null
300
400
  try {
301
- // 简化处理,实际需要实现图片加载逻辑
302
- marker?.setIcon(BitmapDescriptorFactory.defaultMarker())
401
+ val urlConnection = URL(url)
402
+ connection = urlConnection.openConnection() as HttpURLConnection
403
+ connection.connectTimeout = 10000
404
+ connection.readTimeout = 10000
405
+ connection.doInput = true
406
+ connection.connect()
407
+
408
+ if (connection.responseCode == HttpURLConnection.HTTP_OK) {
409
+ inputStream = connection.inputStream
410
+ val bitmap = BitmapFactory.decodeStream(inputStream)
411
+ callback(bitmap)
412
+ } else {
413
+ callback(null)
414
+ }
303
415
  } catch (e: Exception) {
304
- e.printStackTrace()
416
+ callback(null)
417
+ } finally {
418
+ inputStream?.close()
419
+ connection?.disconnect()
305
420
  }
306
421
  }
307
422
  }
308
423
 
424
+ /**
425
+ * 调整图片尺寸
426
+ */
427
+ private fun resizeBitmap(bitmap: Bitmap, width: Int, height: Int): Bitmap {
428
+ // 如果没有指定尺寸,使用原图尺寸或默认值
429
+ val finalWidth = if (width > 0) width else bitmap.width
430
+ val finalHeight = if (height > 0) height else bitmap.height
431
+
432
+ return if (bitmap.width == finalWidth && bitmap.height == finalHeight) {
433
+ bitmap
434
+ } else {
435
+ Bitmap.createScaledBitmap(bitmap, finalWidth, finalHeight, true)
436
+ }
437
+ }
438
+
439
+ /**
440
+ * 设置大头针颜色
441
+ */
442
+ fun setPinColor(color: String?) {
443
+ pendingPinColor = color
444
+ // 颜色变化时需要重新创建 marker
445
+ aMap?.let { map ->
446
+ marker?.let { oldMarker ->
447
+ val position = oldMarker.position
448
+ oldMarker.remove()
449
+ marker = null
450
+
451
+ createOrUpdateMarker()
452
+ marker?.position = position
453
+ }
454
+ }
455
+ }
456
+
457
+ /**
458
+ * 应用大头针颜色
459
+ */
460
+ private fun applyPinColor(color: String, marker: Marker) {
461
+ try {
462
+ val hue = when (color.lowercase()) {
463
+ "red" -> BitmapDescriptorFactory.HUE_RED
464
+ "orange" -> BitmapDescriptorFactory.HUE_ORANGE
465
+ "yellow" -> BitmapDescriptorFactory.HUE_YELLOW
466
+ "green" -> BitmapDescriptorFactory.HUE_GREEN
467
+ "cyan" -> BitmapDescriptorFactory.HUE_CYAN
468
+ "blue" -> BitmapDescriptorFactory.HUE_BLUE
469
+ "violet" -> BitmapDescriptorFactory.HUE_VIOLET
470
+ "magenta" -> BitmapDescriptorFactory.HUE_MAGENTA
471
+ "rose" -> BitmapDescriptorFactory.HUE_ROSE
472
+ "purple" -> BitmapDescriptorFactory.HUE_VIOLET
473
+ else -> BitmapDescriptorFactory.HUE_RED
474
+ }
475
+ marker.setIcon(BitmapDescriptorFactory.defaultMarker(hue))
476
+ } catch (e: Exception) {
477
+ // 忽略异常
478
+ }
479
+ }
480
+
309
481
  /**
310
482
  * 设置 z-index
311
483
  */
312
484
  fun setZIndex(zIndex: Float) {
485
+ pendingZIndex = zIndex
313
486
  marker?.let { it.zIndex = zIndex }
314
487
  }
315
488
 
316
489
  /**
317
490
  * 设置图标宽度(用于自定义图标 icon 属性)
491
+ * 注意:React Native 传入的是 DP 值,需要转换为 PX
318
492
  */
319
493
  fun setIconWidth(width: Int) {
320
- iconWidth = width
494
+ val density = context.resources.displayMetrics.density
495
+ iconWidth = (width * density).toInt()
321
496
  }
322
497
 
323
498
  /**
324
499
  * 设置图标高度(用于自定义图标 icon 属性)
500
+ * 注意:React Native 传入的是 DP 值,需要转换为 PX
325
501
  */
326
502
  fun setIconHeight(height: Int) {
327
- iconHeight = height
503
+ val density = context.resources.displayMetrics.density
504
+ iconHeight = (height * density).toInt()
328
505
  }
329
506
 
330
507
  /**
@@ -345,6 +522,64 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
345
522
  customViewHeight = (height * density).toInt()
346
523
  }
347
524
 
525
+ /**
526
+ * 全局的 Marker 点击监听器
527
+ * 必须在 ExpoGaodeMapView 中设置,不能在每个 MarkerView 中重复设置
528
+ */
529
+ companion object {
530
+ private val markerViewMap = mutableMapOf<Marker, MarkerView>()
531
+
532
+ fun registerMarker(marker: Marker, view: MarkerView) {
533
+ markerViewMap[marker] = view
534
+ }
535
+
536
+ fun unregisterMarker(marker: Marker) {
537
+ markerViewMap.remove(marker)
538
+ }
539
+
540
+ fun handleMarkerClick(marker: Marker): Boolean {
541
+ markerViewMap[marker]?.let { view ->
542
+ view.onMarkerPress(mapOf(
543
+ "latitude" to marker.position.latitude,
544
+ "longitude" to marker.position.longitude
545
+ ))
546
+ // 显示信息窗口(如果有 title 或 snippet)
547
+ if (!marker.title.isNullOrEmpty() || !marker.snippet.isNullOrEmpty()) {
548
+ marker.showInfoWindow()
549
+ }
550
+ return true
551
+ }
552
+ return false
553
+ }
554
+
555
+ fun handleMarkerDragStart(marker: Marker) {
556
+ markerViewMap[marker]?.let { view ->
557
+ view.onMarkerDragStart(mapOf(
558
+ "latitude" to marker.position.latitude,
559
+ "longitude" to marker.position.longitude
560
+ ))
561
+ }
562
+ }
563
+
564
+ fun handleMarkerDrag(marker: Marker) {
565
+ markerViewMap[marker]?.let { view ->
566
+ view.onMarkerDrag(mapOf(
567
+ "latitude" to marker.position.latitude,
568
+ "longitude" to marker.position.longitude
569
+ ))
570
+ }
571
+ }
572
+
573
+ fun handleMarkerDragEnd(marker: Marker) {
574
+ markerViewMap[marker]?.let { view ->
575
+ view.onMarkerDragEnd(mapOf(
576
+ "latitude" to marker.position.latitude,
577
+ "longitude" to marker.position.longitude
578
+ ))
579
+ }
580
+ }
581
+ }
582
+
348
583
  /**
349
584
  * 创建或更新标记
350
585
  */
@@ -354,52 +589,28 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
354
589
  val options = MarkerOptions()
355
590
  marker = map.addMarker(options)
356
591
 
357
- map.setOnMarkerClickListener { clickedMarker ->
358
- if (clickedMarker == marker) {
359
- onPress(mapOf(
360
- "latitude" to clickedMarker.position.latitude,
361
- "longitude" to clickedMarker.position.longitude
362
- ))
363
- true
364
- } else {
365
- false
366
- }
367
- }
368
-
369
- map.setOnMarkerDragListener(object : AMap.OnMarkerDragListener {
370
- override fun onMarkerDragStart(draggedMarker: Marker?) {
371
- if (draggedMarker == marker) {
372
- draggedMarker?.let {
373
- onDragStart(mapOf(
374
- "latitude" to it.position.latitude,
375
- "longitude" to it.position.longitude
376
- ))
377
- }
378
- }
379
- }
592
+ // 注册到全局 map
593
+ marker?.let { m ->
594
+ registerMarker(m, this)
380
595
 
381
- override fun onMarkerDrag(draggedMarker: Marker?) {
382
- if (draggedMarker == marker) {
383
- draggedMarker?.let {
384
- onDrag(mapOf(
385
- "latitude" to it.position.latitude,
386
- "longitude" to it.position.longitude
387
- ))
388
- }
389
- }
390
- }
596
+ // 应用缓存的属性
597
+ pendingTitle?.let { m.title = it }
598
+ pendingSnippet?.let { m.snippet = it }
599
+ pendingDraggable?.let { m.isDraggable = it }
600
+ pendingOpacity?.let { m.alpha = it }
601
+ pendingFlat?.let { m.isFlat = it }
602
+ pendingZIndex?.let { m.zIndex = it }
603
+ pendingAnchor?.let { m.setAnchor(it.first, it.second) }
391
604
 
392
- override fun onMarkerDragEnd(draggedMarker: Marker?) {
393
- if (draggedMarker == marker) {
394
- draggedMarker?.let {
395
- onDragEnd(mapOf(
396
- "latitude" to it.position.latitude,
397
- "longitude" to it.position.longitude
398
- ))
399
- }
605
+ // 优先级:children > icon > pinColor
606
+ if (childCount == 0) {
607
+ if (pendingIconUri != null) {
608
+ loadAndSetIcon(pendingIconUri!!, m)
609
+ } else if (pendingPinColor != null) {
610
+ applyPinColor(pendingPinColor!!, m)
400
611
  }
401
612
  }
402
- })
613
+ }
403
614
  }
404
615
  }
405
616
  }
@@ -468,7 +679,6 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
468
679
 
469
680
  bitmap
470
681
  } catch (e: Exception) {
471
- android.util.Log.e("MarkerView", "创建 Bitmap 失败", e)
472
682
  null
473
683
  }
474
684
  }
@@ -519,15 +729,12 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
519
729
  try {
520
730
  if (child != null && indexOfChild(child) >= 0) {
521
731
  super.removeView(child)
522
- mainHandler.postDelayed({
523
- if (childCount == 0 && marker != null) {
524
- marker?.setIcon(BitmapDescriptorFactory.defaultMarker())
525
- marker?.setAnchor(0.5f, 1.0f)
526
- }
527
- }, 50)
732
+ // 不要在这里恢复默认图标
733
+ // 如果 MarkerView 整体要被移除,onDetachedFromWindow 会处理
734
+ // 如果只是移除 children 并保留 Marker,应该由外部重新设置 children
528
735
  }
529
736
  } catch (e: Exception) {
530
- android.util.Log.e("MarkerView", "removeView 异常", e)
737
+ // 忽略异常
531
738
  }
532
739
  }
533
740
 
@@ -535,17 +742,19 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
535
742
  try {
536
743
  if (index >= 0 && index < childCount) {
537
744
  super.removeViewAt(index)
538
- mainHandler.postDelayed({
539
- if (childCount == 0 && marker != null) {
540
- marker?.setIcon(BitmapDescriptorFactory.defaultMarker())
541
- marker?.setAnchor(0.5f, 1.0f)
542
- } else if (childCount > 0 && marker != null) {
543
- updateMarkerIcon()
544
- }
545
- }, 50)
745
+ // 只在还有子视图时更新图标
746
+ if (!isRemoving && childCount > 1 && marker != null) {
747
+ mainHandler.postDelayed({
748
+ if (!isRemoving && marker != null && childCount > 0) {
749
+ updateMarkerIcon()
750
+ }
751
+ }, 50)
752
+ }
753
+ // 如果最后一个子视图被移除,什么都不做
754
+ // 让 onDetachedFromWindow 处理完整的清理
546
755
  }
547
756
  } catch (e: Exception) {
548
- android.util.Log.e("MarkerView", "removeViewAt 异常", e)
757
+ // 忽略异常
549
758
  }
550
759
  }
551
760
  /**
@@ -569,6 +778,9 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
569
778
 
570
779
 
571
780
  override fun addView(child: View?, index: Int, params: android.view.ViewGroup.LayoutParams?) {
781
+ // 🔑 关键修复:记录添加前的子视图数量
782
+ val childCountBefore = childCount
783
+
572
784
  val finalParams = android.widget.LinearLayout.LayoutParams(
573
785
  if (customViewWidth > 0) customViewWidth else android.widget.LinearLayout.LayoutParams.WRAP_CONTENT,
574
786
  if (customViewHeight > 0) customViewHeight else android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
@@ -587,20 +799,31 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
587
799
  fixChildLayoutParams(it)
588
800
  }
589
801
 
590
- mainHandler.postDelayed({
591
- if (marker != null) updateMarkerIcon()
592
- }, 50)
593
-
594
- mainHandler.postDelayed({
595
- if (marker != null) updateMarkerIcon()
596
- }, 150)
802
+ // 🔑 关键修复:只有在子视图数量真正变化且 marker 已存在时才更新图标
803
+ // 避免在其他覆盖物添加时触发不必要的刷新
804
+ if (!isRemoving && marker != null && childCount > childCountBefore) {
805
+ mainHandler.postDelayed({
806
+ if (!isRemoving && marker != null) {
807
+ updateMarkerIcon()
808
+ }
809
+ }, 50)
810
+
811
+ mainHandler.postDelayed({
812
+ if (!isRemoving && marker != null) {
813
+ updateMarkerIcon()
814
+ }
815
+ }, 150)
816
+ }
597
817
  }
598
818
 
599
819
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
600
820
  super.onLayout(changed, left, top, right, bottom)
601
- if (changed && childCount > 0) {
821
+ // 🔑 关键修复:只有在有子视图且 marker 存在时才更新,避免不必要的刷新
822
+ if (changed && !isRemoving && childCount > 0 && marker != null) {
602
823
  mainHandler.postDelayed({
603
- if (marker != null) updateMarkerIcon()
824
+ if (!isRemoving && marker != null) {
825
+ updateMarkerIcon()
826
+ }
604
827
  }, 200)
605
828
  }
606
829
  }
@@ -609,13 +832,19 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
609
832
  * 移除标记
610
833
  */
611
834
  fun removeMarker() {
612
- marker?.remove()
835
+ marker?.let {
836
+ unregisterMarker(it)
837
+ it.remove()
838
+ }
613
839
  marker = null
614
840
  }
615
841
 
616
842
  override fun onDetachedFromWindow() {
617
843
  super.onDetachedFromWindow()
618
844
 
845
+ // 标记正在移除
846
+ isRemoving = true
847
+
619
848
  // 清理所有延迟任务
620
849
  mainHandler.removeCallbacksAndMessages(null)
621
850
 
@@ -11,7 +11,7 @@ class MarkerViewModule : Module() {
11
11
  Name("MarkerView")
12
12
 
13
13
  View(MarkerView::class) {
14
- Events("onPress", "onDragStart", "onDrag", "onDragEnd")
14
+ Events("onMarkerPress", "onMarkerDragStart", "onMarkerDrag", "onMarkerDragEnd")
15
15
 
16
16
  // 拆分 position 为两个独立属性以兼容 React Native 旧架构
17
17
  Prop<Double>("latitude") { view, lat ->
@@ -26,14 +26,22 @@ class MarkerViewModule : Module() {
26
26
  view.setTitle(title)
27
27
  }
28
28
 
29
- Prop<String>("description") { view, description ->
30
- view.setDescription(description)
29
+ Prop<String>("snippet") { view, snippet ->
30
+ view.setDescription(snippet)
31
31
  }
32
32
 
33
33
  Prop<Boolean>("draggable") { view, draggable ->
34
34
  view.setDraggable(draggable)
35
35
  }
36
36
 
37
+ Prop<String>("icon") { view, icon ->
38
+ view.setMarkerIcon(icon)
39
+ }
40
+
41
+ Prop<String>("pinColor") { view, color ->
42
+ view.setPinColor(color)
43
+ }
44
+
37
45
  Prop<Float>("opacity") { view, opacity ->
38
46
  view.setOpacity(opacity)
39
47
  }
@@ -38,7 +38,8 @@ class MultiPointView(context: Context, appContext: AppContext) : ExpoView(contex
38
38
  val lng = (point["longitude"] as? Number)?.toDouble()
39
39
  val id = point["id"] as? String ?: ""
40
40
 
41
- if (lat != null && lng != null) {
41
+ // 坐标验证
42
+ if (lat != null && lng != null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
42
43
  val multiPointItem = MultiPointItem(LatLng(lat, lng))
43
44
  multiPointItem.customerId = id
44
45
  points.add(multiPointItem)
@@ -94,10 +95,12 @@ class MultiPointView(context: Context, appContext: AppContext) : ExpoView(contex
94
95
  fun removeMultiPoint() {
95
96
  multiPointOverlay?.remove()
96
97
  multiPointOverlay = null
98
+ points.clear()
97
99
  }
98
100
 
99
101
  override fun onDetachedFromWindow() {
100
102
  super.onDetachedFromWindow()
101
103
  removeMultiPoint()
104
+ aMap = null
102
105
  }
103
106
  }