expo-gaode-map 1.1.6 → 1.1.7

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.
@@ -17,7 +17,7 @@ import expo.modules.gaodemap.overlays.*
17
17
 
18
18
  /**
19
19
  * 高德地图视图组件
20
- *
20
+ *
21
21
  * 负责:
22
22
  * - 地图视图的创建和管理
23
23
  * - 地图事件的派发
@@ -26,643 +26,653 @@ import expo.modules.gaodemap.overlays.*
26
26
  */
27
27
  @Suppress("ViewConstructor")
28
28
  class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
29
-
30
- /**
31
- * 拦截 React Native 的 ViewManager 操作
32
- * 重写 requestLayout 防止在移除视图时触发布局异常
33
- */
34
- override fun requestLayout() {
35
- try {
36
- super.requestLayout()
37
- } catch (e: Exception) {
38
- Log.e(TAG, "requestLayout 异常被捕获", e)
39
- }
40
- }
41
-
42
- companion object {
43
- private const val TAG = "ExpoGaodeMapView"
44
- }
45
-
46
- // Props 存储
47
- /** 地图类型 */
48
- internal var mapType: Int = 0
49
- /** 初始相机位置 */
50
- internal var initialCameraPosition: Map<String, Any?>? = null
51
- /** 是否跟随用户位置 */
52
- internal var followUserLocation: Boolean = false
53
-
54
- /** 主线程 Handler */
55
- private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
56
-
57
- // 事件派发器
58
- private val onMapPress by EventDispatcher()
59
- private val onMapLongPress by EventDispatcher()
60
- private val onLoad by EventDispatcher()
61
- private val onLocation by EventDispatcher()
62
- private val onMarkerPress by EventDispatcher()
63
- private val onMarkerDragStart by EventDispatcher()
64
- private val onMarkerDrag by EventDispatcher()
65
- private val onMarkerDragEnd by EventDispatcher()
66
- private val onCirclePress by EventDispatcher()
67
- private val onPolygonPress by EventDispatcher()
68
- private val onPolylinePress by EventDispatcher()
69
-
70
- // 高德地图视图
71
- private lateinit var mapView: MapView
72
- private lateinit var aMap: AMap
73
-
74
- // 管理器
75
- private lateinit var cameraManager: CameraManager
76
- private lateinit var uiManager: UIManager
77
- private lateinit var overlayManager: OverlayManager
78
-
79
- // 缓存初始相机位置,等待地图加载完成后设置
80
- private var pendingCameraPosition: Map<String, Any?>? = null
81
- private var isMapLoaded = false
82
-
83
- init {
84
- try {
85
- // 确保隐私合规已设置
86
- MapsInitializer.updatePrivacyShow(context, true, true)
87
- MapsInitializer.updatePrivacyAgree(context, true)
88
-
89
- // 创建地图视图
90
- mapView = MapView(context)
91
- mapView.onCreate(null)
92
- aMap = mapView.map
93
-
94
- // 初始化管理器
95
- cameraManager = CameraManager(aMap)
96
- uiManager = UIManager(aMap, context).apply {
97
- // 设置定位变化回调
98
- onLocationChanged = { latitude, longitude, accuracy ->
99
- this@ExpoGaodeMapView.onLocation(mapOf(
100
- "latitude" to latitude,
101
- "longitude" to longitude,
102
- "accuracy" to accuracy.toDouble(),
103
- "timestamp" to System.currentTimeMillis()
104
- ))
29
+
30
+ /**
31
+ * 拦截 React Native 的 ViewManager 操作
32
+ * 重写 requestLayout 防止在移除视图时触发布局异常
33
+ */
34
+ override fun requestLayout() {
35
+ try {
36
+ super.requestLayout()
37
+ } catch (e: Exception) {
38
+ Log.e(TAG, "requestLayout 异常被捕获", e)
39
+ }
40
+ }
41
+
42
+ companion object {
43
+ private const val TAG = "ExpoGaodeMapView"
44
+ }
45
+
46
+ // Props 存储
47
+ /** 地图类型 */
48
+ internal var mapType: Int = 0
49
+ /** 初始相机位置 */
50
+ internal var initialCameraPosition: Map<String, Any?>? = null
51
+ /** 是否跟随用户位置 */
52
+ internal var followUserLocation: Boolean = false
53
+
54
+ /** 主线程 Handler */
55
+ private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
56
+
57
+ // 事件派发器
58
+ private val onMapPress by EventDispatcher()
59
+ private val onMapLongPress by EventDispatcher()
60
+ private val onLoad by EventDispatcher()
61
+ private val onLocation by EventDispatcher()
62
+ private val onMarkerPress by EventDispatcher()
63
+ private val onMarkerDragStart by EventDispatcher()
64
+ private val onMarkerDrag by EventDispatcher()
65
+ private val onMarkerDragEnd by EventDispatcher()
66
+ private val onCirclePress by EventDispatcher()
67
+ private val onPolygonPress by EventDispatcher()
68
+ private val onPolylinePress by EventDispatcher()
69
+
70
+ // 高德地图视图
71
+ private lateinit var mapView: MapView
72
+ private lateinit var aMap: AMap
73
+
74
+ // 管理器
75
+ private lateinit var cameraManager: CameraManager
76
+ private lateinit var uiManager: UIManager
77
+ private lateinit var overlayManager: OverlayManager
78
+
79
+ // 缓存初始相机位置,等待地图加载完成后设置
80
+ private var pendingCameraPosition: Map<String, Any?>? = null
81
+ private var isMapLoaded = false
82
+
83
+ init {
84
+ try {
85
+ // 确保隐私合规已设置
86
+ MapsInitializer.updatePrivacyShow(context, true, true)
87
+ MapsInitializer.updatePrivacyAgree(context, true)
88
+
89
+ // 创建地图视图
90
+ mapView = MapView(context)
91
+ mapView.onCreate(null)
92
+ aMap = mapView.map
93
+
94
+ // 初始化管理器
95
+ cameraManager = CameraManager(aMap)
96
+ uiManager = UIManager(aMap, context).apply {
97
+ // 设置定位变化回调
98
+ onLocationChanged = { latitude, longitude, accuracy ->
99
+ this@ExpoGaodeMapView.onLocation(mapOf(
100
+ "latitude" to latitude,
101
+ "longitude" to longitude,
102
+ "accuracy" to accuracy.toDouble(),
103
+ "timestamp" to System.currentTimeMillis()
104
+ ))
105
+ }
106
+ }
107
+ overlayManager = OverlayManager(aMap, context).apply {
108
+ onMarkerPress = { id, lat, lng ->
109
+ this@ExpoGaodeMapView.onMarkerPress(mapOf(
110
+ "markerId" to id,
111
+ "latitude" to lat,
112
+ "longitude" to lng
113
+ ))
114
+ }
115
+ onMarkerDragStart = { id, lat, lng ->
116
+ this@ExpoGaodeMapView.onMarkerDragStart(mapOf(
117
+ "markerId" to id,
118
+ "latitude" to lat,
119
+ "longitude" to lng
120
+ ))
121
+ }
122
+ onMarkerDrag = { id, lat, lng ->
123
+ this@ExpoGaodeMapView.onMarkerDrag(mapOf(
124
+ "markerId" to id,
125
+ "latitude" to lat,
126
+ "longitude" to lng
127
+ ))
128
+ }
129
+ onMarkerDragEnd = { id, lat, lng ->
130
+ this@ExpoGaodeMapView.onMarkerDragEnd(mapOf(
131
+ "markerId" to id,
132
+ "latitude" to lat,
133
+ "longitude" to lng
134
+ ))
135
+ }
136
+ onCirclePress = { id, lat, lng ->
137
+ this@ExpoGaodeMapView.onCirclePress(mapOf(
138
+ "circleId" to id,
139
+ "latitude" to lat,
140
+ "longitude" to lng
141
+ ))
142
+ }
143
+ onPolygonPress = { id, lat, lng ->
144
+ this@ExpoGaodeMapView.onPolygonPress(mapOf(
145
+ "polygonId" to id,
146
+ "latitude" to lat,
147
+ "longitude" to lng
148
+ ))
149
+ }
150
+ onPolylinePress = { id, lat, lng ->
151
+ this@ExpoGaodeMapView.onPolylinePress(mapOf(
152
+ "polylineId" to id,
153
+ "latitude" to lat,
154
+ "longitude" to lng
155
+ ))
156
+ }
157
+ }
158
+
159
+ // 添加地图视图到布局
160
+ addView(mapView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
161
+
162
+ // 设置地图事件监听
163
+ setupMapListeners()
164
+
165
+ // 地图加载完成回调
166
+ aMap.setOnMapLoadedListener {
167
+ isMapLoaded = true
168
+
169
+ // 应用缓存的 Props
170
+ if (mapType != 0) {
171
+ setMapType(mapType)
172
+ }
173
+
174
+ val positionToApply = initialCameraPosition ?: pendingCameraPosition
175
+ positionToApply?.let { position ->
176
+ applyInitialCameraPosition(position)
177
+ pendingCameraPosition = null
178
+ }
179
+
180
+ onLoad(mapOf("loaded" to true))
181
+ }
182
+ } catch (e: Exception) {
183
+ Log.e(TAG, "ExpoGaodeMapView 初始化失败", e)
184
+ }
185
+ }
186
+
187
+ /**
188
+ * 设置地图事件监听
189
+ */
190
+ private fun setupMapListeners() {
191
+ aMap.setOnMapClickListener { latLng ->
192
+ // 检查声明式 PolylineView
193
+ if (checkDeclarativePolylinePress(latLng)) {
194
+ return@setOnMapClickListener
195
+ }
196
+
197
+ // 检查声明式 PolygonView
198
+ if (checkDeclarativePolygonPress(latLng)) {
199
+ return@setOnMapClickListener
200
+ }
201
+
202
+ // 检查声明式 CircleView
203
+ if (checkDeclarativeCirclePress(latLng)) {
204
+ return@setOnMapClickListener
205
+ }
206
+
207
+ // 检查命令式圆形
208
+ val circleId = overlayManager.checkCirclePress(latLng)
209
+ if (circleId != null) {
210
+ overlayManager.onCirclePress?.invoke(circleId, latLng.latitude, latLng.longitude)
211
+ return@setOnMapClickListener
212
+ }
213
+
214
+ // 检查命令式多边形
215
+ val polygonId = overlayManager.checkPolygonPress(latLng)
216
+ if (polygonId != null) {
217
+ overlayManager.onPolygonPress?.invoke(polygonId, latLng.latitude, latLng.longitude)
218
+ return@setOnMapClickListener
219
+ }
220
+
221
+ // 检查命令式折线
222
+ val polylineId = overlayManager.checkPolylinePress(latLng)
223
+ if (polylineId != null) {
224
+ overlayManager.onPolylinePress?.invoke(polylineId, latLng.latitude, latLng.longitude)
225
+ return@setOnMapClickListener
226
+ }
227
+
228
+ // 如果都没点击,触发地图点击事件
229
+ onMapPress(mapOf(
230
+ "latitude" to latLng.latitude,
231
+ "longitude" to latLng.longitude
232
+ ))
233
+ }
234
+
235
+ aMap.setOnMapLongClickListener { latLng ->
236
+ onMapLongPress(mapOf(
237
+ "latitude" to latLng.latitude,
238
+ "longitude" to latLng.longitude
239
+ ))
240
+ }
241
+ }
242
+
243
+ // ==================== 地图类型和相机 ====================
244
+
245
+ /**
246
+ * 设置地图类型
247
+ * @param type 地图类型
248
+ */
249
+ fun setMapType(type: Int) {
250
+ mainHandler.post {
251
+ uiManager.setMapType(type)
252
+ }
253
+ }
254
+
255
+ /**
256
+ * 设置初始相机位置
257
+ * @param position 相机位置配置
258
+ */
259
+ fun setInitialCameraPosition(position: Map<String, Any?>) {
260
+ initialCameraPosition = position
261
+
262
+ // 如果地图已加载,立即应用;否则缓存等待地图加载完成
263
+ if (isMapLoaded) {
264
+ mainHandler.post {
265
+ applyInitialCameraPosition(position)
266
+ }
267
+ } else {
268
+ pendingCameraPosition = position
269
+ }
270
+ }
271
+
272
+ /**
273
+ * 实际应用相机位置
274
+ * @param position 相机位置配置
275
+ */
276
+ private fun applyInitialCameraPosition(position: Map<String, Any?>) {
277
+ cameraManager.setInitialCameraPosition(position)
278
+ }
279
+
280
+ // ==================== UI 控件和手势 ====================
281
+
282
+ /** 设置是否显示缩放控件 */
283
+ fun setShowsZoomControls(show: Boolean) = uiManager.setShowsZoomControls(show)
284
+ /** 设置是否显示指南针 */
285
+ fun setShowsCompass(show: Boolean) = uiManager.setShowsCompass(show)
286
+ /** 设置是否显示比例尺 */
287
+ fun setShowsScale(show: Boolean) = uiManager.setShowsScale(show)
288
+
289
+ /** 设置是否启用缩放手势 */
290
+ fun setZoomEnabled(enabled: Boolean) = uiManager.setZoomEnabled(enabled)
291
+ /** 设置是否启用滚动手势 */
292
+ fun setScrollEnabled(enabled: Boolean) = uiManager.setScrollEnabled(enabled)
293
+ /** 设置是否启用旋转手势 */
294
+ fun setRotateEnabled(enabled: Boolean) = uiManager.setRotateEnabled(enabled)
295
+ /** 设置是否启用倾斜手势 */
296
+ fun setTiltEnabled(enabled: Boolean) = uiManager.setTiltEnabled(enabled)
297
+
298
+ /** 设置最大缩放级别 */
299
+ fun setMaxZoom(maxZoom: Float) = cameraManager.setMaxZoomLevel(maxZoom)
300
+ /** 设置最小缩放级别 */
301
+ fun setMinZoom(minZoom: Float) = cameraManager.setMinZoomLevel(minZoom)
302
+
303
+ /** 设置是否显示用户位置 */
304
+ fun setShowsUserLocation(show: Boolean) {
305
+ mainHandler.post {
306
+ uiManager.setShowsUserLocation(show, followUserLocation)
307
+ }
308
+ }
309
+
310
+ /**
311
+ * 设置是否跟随用户位置
312
+ * @param follow 是否跟随
313
+ */
314
+ fun setFollowUserLocation(follow: Boolean) {
315
+ followUserLocation = follow
316
+ // 如果定位已开启,立即应用新设置
317
+ mainHandler.post {
318
+ if (aMap.isMyLocationEnabled) {
319
+ uiManager.setShowsUserLocation(true, follow)
320
+ }
321
+ }
322
+ }
323
+
324
+ /**
325
+ * 设置用户位置样式
326
+ * @param representation 样式配置
327
+ */
328
+ fun setUserLocationRepresentation(representation: Map<String, Any>) {
329
+ uiManager.setUserLocationRepresentation(representation)
330
+ }
331
+
332
+ /** 设置是否显示交通路况 */
333
+ fun setShowsTraffic(show: Boolean) = uiManager.setShowsTraffic(show)
334
+ /** 设置是否显示建筑物 */
335
+ fun setShowsBuildings(show: Boolean) = uiManager.setShowsBuildings(show)
336
+ /** 设置是否显示室内地图 */
337
+ fun setShowsIndoorMap(show: Boolean) = uiManager.setShowsIndoorMap(show)
338
+
339
+ // ==================== 相机控制方法 ====================
340
+
341
+ /**
342
+ * 移动相机
343
+ * @param position 目标位置
344
+ * @param duration 动画时长(毫秒)
345
+ */
346
+ fun moveCamera(position: Map<String, Any>, duration: Int) {
347
+ cameraManager.moveCamera(position, duration)
348
+ }
349
+
350
+ /**
351
+ * 获取屏幕坐标对应的地理坐标
352
+ * @param point 屏幕坐标
353
+ * @return 地理坐标
354
+ */
355
+ fun getLatLng(point: Map<String, Double>): Map<String, Double> {
356
+ return cameraManager.getLatLng(point)
357
+ }
358
+
359
+ /**
360
+ * 设置地图中心点
361
+ * @param center 中心点坐标
362
+ * @param animated 是否动画
363
+ */
364
+ fun setCenter(center: Map<String, Double>, animated: Boolean) {
365
+ cameraManager.setCenter(center, animated)
366
+ }
367
+
368
+ /**
369
+ * 设置地图缩放级别
370
+ * @param zoom 缩放级别
371
+ * @param animated 是否动画
372
+ */
373
+ fun setZoomLevel(zoom: Float, animated: Boolean) {
374
+ cameraManager.setZoomLevel(zoom, animated)
375
+ }
376
+
377
+ /**
378
+ * 获取当前相机位置
379
+ * @return 相机位置信息
380
+ */
381
+ fun getCameraPosition(): Map<String, Any> {
382
+ return cameraManager.getCameraPosition()
383
+ }
384
+
385
+ // ==================== 覆盖物管理 ====================
386
+
387
+ /** 添加圆形覆盖物 */
388
+ fun addCircle(id: String, props: Map<String, Any>) {
389
+ mainHandler.post {
390
+ overlayManager.addCircle(id, props)
391
+ }
392
+ }
393
+
394
+ /** 移除圆形覆盖物 */
395
+ fun removeCircle(id: String) {
396
+ mainHandler.post {
397
+ overlayManager.removeCircle(id)
398
+ }
399
+ }
400
+
401
+ /** 更新圆形覆盖物 */
402
+ fun updateCircle(id: String, props: Map<String, Any>) {
403
+ mainHandler.post {
404
+ overlayManager.updateCircle(id, props)
405
+ }
406
+ }
407
+
408
+ /** 添加标记点 */
409
+ fun addMarker(id: String, props: Map<String, Any>) {
410
+ mainHandler.post {
411
+ overlayManager.addMarker(id, props)
412
+ }
413
+ }
414
+
415
+ /** 移除标记点 */
416
+ fun removeMarker(id: String) {
417
+ mainHandler.post {
418
+ overlayManager.removeMarker(id)
105
419
  }
106
- }
107
- overlayManager = OverlayManager(aMap, context).apply {
108
- onMarkerPress = { id, lat, lng ->
109
- this@ExpoGaodeMapView.onMarkerPress(mapOf(
110
- "markerId" to id,
111
- "latitude" to lat,
112
- "longitude" to lng
113
- ))
420
+ }
421
+
422
+ /** 更新标记点 */
423
+ fun updateMarker(id: String, props: Map<String, Any>) {
424
+ mainHandler.post {
425
+ overlayManager.updateMarker(id, props)
114
426
  }
115
- onMarkerDragStart = { id, lat, lng ->
116
- this@ExpoGaodeMapView.onMarkerDragStart(mapOf(
117
- "markerId" to id,
118
- "latitude" to lat,
119
- "longitude" to lng
120
- ))
427
+ }
428
+
429
+ /** 添加折线 */
430
+ fun addPolyline(id: String, props: Map<String, Any>) {
431
+ mainHandler.post {
432
+ overlayManager.addPolyline(id, props)
433
+ }
434
+ }
435
+
436
+ /** 移除折线 */
437
+ fun removePolyline(id: String) {
438
+ mainHandler.post {
439
+ overlayManager.removePolyline(id)
440
+ }
441
+ }
442
+
443
+ /** 更新折线 */
444
+ fun updatePolyline(id: String, props: Map<String, Any>) {
445
+ mainHandler.post {
446
+ overlayManager.updatePolyline(id, props)
447
+ }
448
+ }
449
+
450
+ /** 添加多边形 */
451
+ fun addPolygon(id: String, props: Map<String, Any>) {
452
+ mainHandler.post {
453
+ overlayManager.addPolygon(id, props)
454
+ }
455
+ }
456
+
457
+ /** 移除多边形 */
458
+ fun removePolygon(id: String) {
459
+ mainHandler.post {
460
+ overlayManager.removePolygon(id)
461
+ }
462
+ }
463
+
464
+ /** 更新多边形 */
465
+ fun updatePolygon(id: String, props: Map<String, Any>) {
466
+ mainHandler.post {
467
+ overlayManager.updatePolygon(id, props)
468
+ }
469
+ }
470
+
471
+ // ==================== 生命周期方法 ====================
472
+
473
+ /** 恢复地图 */
474
+ @Suppress("unused")
475
+ fun onResume() {
476
+ mapView.onResume()
477
+ }
478
+
479
+ /** 暂停地图 */
480
+ @Suppress("unused")
481
+ fun onPause() {
482
+ mapView.onPause()
483
+ }
484
+
485
+ /** 销毁地图 */
486
+ @Suppress("unused")
487
+ fun onDestroy() {
488
+ // 清理 Handler 回调,防止内存泄露
489
+ mainHandler.removeCallbacksAndMessages(null)
490
+
491
+ // 清理地图监听器
492
+ aMap.setOnMapClickListener(null)
493
+ aMap.setOnMapLongClickListener(null)
494
+ aMap.setOnMapLoadedListener(null)
495
+
496
+ // 清理覆盖物
497
+ overlayManager.clear()
498
+
499
+ // 清理 MarkerView 列表
500
+ markerViews.clear()
501
+
502
+ // 销毁地图
503
+ mapView.onDestroy()
504
+ }
505
+
506
+ /** 保存实例状态 */
507
+ @Suppress("unused")
508
+ fun onSaveInstanceState(outState: android.os.Bundle) {
509
+ mapView.onSaveInstanceState(outState)
510
+ }
511
+
512
+ /**
513
+ * 存储 MarkerView 引用,因为它们不在视图层级中
514
+ */
515
+ private val markerViews = mutableListOf<MarkerView>()
516
+
517
+ /**
518
+ * 添加子视图时自动连接到地图
519
+ *
520
+ * 关键修复:MarkerView 不进入视图层级,但要确保 React Native 追踪正确
521
+ */
522
+ override fun addView(child: View?, index: Int) {
523
+ if (child is MarkerView) {
524
+ child.setMap(aMap)
525
+ markerViews.add(child)
526
+ return
121
527
  }
122
- onMarkerDrag = { id, lat, lng ->
123
- this@ExpoGaodeMapView.onMarkerDrag(mapOf(
124
- "markerId" to id,
125
- "latitude" to lat,
126
- "longitude" to lng
127
- ))
528
+
529
+ if (child is com.amap.api.maps.MapView) {
530
+ super.addView(child, index)
531
+ return
128
532
  }
129
- onMarkerDragEnd = { id, lat, lng ->
130
- this@ExpoGaodeMapView.onMarkerDragEnd(mapOf(
131
- "markerId" to id,
132
- "latitude" to lat,
133
- "longitude" to lng
134
- ))
533
+
534
+ super.addView(child, index)
535
+
536
+ child?.let {
537
+ when (it) {
538
+ is PolylineView -> it.setMap(aMap)
539
+ is PolygonView -> it.setMap(aMap)
540
+ is CircleView -> it.setMap(aMap)
541
+ is HeatMapView -> it.setMap(aMap)
542
+ is MultiPointView -> it.setMap(aMap)
543
+ is ClusterView -> it.setMap(aMap)
544
+ }
135
545
  }
136
- onCirclePress = { id, lat, lng ->
137
- this@ExpoGaodeMapView.onCirclePress(mapOf(
138
- "circleId" to id,
139
- "latitude" to lat,
140
- "longitude" to lng
141
- ))
546
+ }
547
+
548
+ /**
549
+ * 移除子视图
550
+ */
551
+ override fun removeView(child: View?) {
552
+ if (child is MarkerView) {
553
+ markerViews.remove(child)
554
+ child.removeMarker()
555
+ return
142
556
  }
143
- onPolygonPress = { id, lat, lng ->
144
- this@ExpoGaodeMapView.onPolygonPress(mapOf(
145
- "polygonId" to id,
146
- "latitude" to lat,
147
- "longitude" to lng
148
- ))
557
+
558
+ try {
559
+ super.removeView(child)
560
+ } catch (e: Exception) {
561
+ Log.e(TAG, "removeView 异常", e)
149
562
  }
150
- onPolylinePress = { id, lat, lng ->
151
- this@ExpoGaodeMapView.onPolylinePress(mapOf(
152
- "polylineId" to id,
153
- "latitude" to lat,
154
- "longitude" to lng
155
- ))
563
+ }
564
+
565
+ /**
566
+ * 按索引移除视图
567
+ *
568
+ * 终极修复:完全忽略所有无效的移除请求
569
+ */
570
+ override fun removeViewAt(index: Int) {
571
+ try {
572
+ val actualChildCount = super.getChildCount()
573
+
574
+ if (index >= actualChildCount) {
575
+ val markerIndex = index - actualChildCount
576
+ if (markerIndex >= 0 && markerIndex < markerViews.size) {
577
+ val markerView = markerViews.removeAt(markerIndex)
578
+ markerView.removeMarker()
579
+ return
580
+ } else {
581
+ return
582
+ }
583
+ }
584
+
585
+ if (actualChildCount == 0) {
586
+ return
587
+ }
588
+
589
+ val child = super.getChildAt(index)
590
+
591
+ if (child is com.amap.api.maps.MapView) {
592
+ Log.e(TAG, "阻止移除 MapView")
593
+ return
594
+ }
595
+
596
+ if (child is MarkerView) {
597
+ removeView(child)
598
+ return
599
+ }
600
+
601
+ super.removeViewAt(index)
602
+
603
+ } catch (e: IllegalArgumentException) {
604
+ // 忽略索引异常
605
+ } catch (e: IndexOutOfBoundsException) {
606
+ // 忽略越界异常
607
+ } catch (e: Exception) {
608
+ Log.e(TAG, "移除视图异常", e)
156
609
  }
157
- }
158
-
159
- // 添加地图视图到布局
160
- addView(mapView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
161
-
162
- // 设置地图事件监听
163
- setupMapListeners()
164
-
165
- // 地图加载完成回调
166
- aMap.setOnMapLoadedListener {
167
- isMapLoaded = true
168
-
169
- // 应用缓存的 Props
170
- if (mapType != 0) {
171
- setMapType(mapType)
610
+ }
611
+
612
+ private fun checkDeclarativePolylinePress(latLng: LatLng): Boolean {
613
+ for (i in 0 until childCount) {
614
+ val child = getChildAt(i)
615
+ if (child is PolylineView) {
616
+ if (child.checkPress(latLng)) {
617
+ return true
618
+ }
619
+ }
172
620
  }
173
-
174
- val positionToApply = initialCameraPosition ?: pendingCameraPosition
175
- positionToApply?.let { position ->
176
- applyInitialCameraPosition(position)
177
- pendingCameraPosition = null
621
+ return false
622
+ }
623
+
624
+ private fun checkDeclarativePolygonPress(latLng: LatLng): Boolean {
625
+ for (i in 0 until childCount) {
626
+ val child = getChildAt(i)
627
+ if (child is PolygonView) {
628
+ if (child.checkPress(latLng)) {
629
+ return true
630
+ }
631
+ }
178
632
  }
179
-
180
- onLoad(mapOf("loaded" to true))
181
- }
182
- } catch (e: Exception) {
183
- Log.e(TAG, "ExpoGaodeMapView 初始化失败", e)
184
- }
185
- }
186
-
187
- /**
188
- * 设置地图事件监听
189
- */
190
- private fun setupMapListeners() {
191
- aMap.setOnMapClickListener { latLng ->
192
- // 检查声明式 PolylineView
193
- if (checkDeclarativePolylinePress(latLng)) {
194
- return@setOnMapClickListener
195
- }
196
-
197
- // 检查声明式 PolygonView
198
- if (checkDeclarativePolygonPress(latLng)) {
199
- return@setOnMapClickListener
200
- }
201
-
202
- // 检查声明式 CircleView
203
- if (checkDeclarativeCirclePress(latLng)) {
204
- return@setOnMapClickListener
205
- }
206
-
207
- // 检查命令式圆形
208
- val circleId = overlayManager.checkCirclePress(latLng)
209
- if (circleId != null) {
210
- overlayManager.onCirclePress?.invoke(circleId, latLng.latitude, latLng.longitude)
211
- return@setOnMapClickListener
212
- }
213
-
214
- // 检查命令式多边形
215
- val polygonId = overlayManager.checkPolygonPress(latLng)
216
- if (polygonId != null) {
217
- overlayManager.onPolygonPress?.invoke(polygonId, latLng.latitude, latLng.longitude)
218
- return@setOnMapClickListener
219
- }
220
-
221
- // 检查命令式折线
222
- val polylineId = overlayManager.checkPolylinePress(latLng)
223
- if (polylineId != null) {
224
- overlayManager.onPolylinePress?.invoke(polylineId, latLng.latitude, latLng.longitude)
225
- return@setOnMapClickListener
226
- }
227
-
228
- // 如果都没点击,触发地图点击事件
229
- onMapPress(mapOf(
230
- "latitude" to latLng.latitude,
231
- "longitude" to latLng.longitude
232
- ))
233
- }
234
-
235
- aMap.setOnMapLongClickListener { latLng ->
236
- onMapLongPress(mapOf(
237
- "latitude" to latLng.latitude,
238
- "longitude" to latLng.longitude
239
- ))
240
- }
241
- }
242
-
243
- // ==================== 地图类型和相机 ====================
244
-
245
- /**
246
- * 设置地图类型
247
- * @param type 地图类型
248
- */
249
- fun setMapType(type: Int) {
250
- mainHandler.post {
251
- uiManager.setMapType(type)
252
- }
253
- }
254
-
255
- /**
256
- * 设置初始相机位置
257
- * @param position 相机位置配置
258
- */
259
- fun setInitialCameraPosition(position: Map<String, Any?>) {
260
- initialCameraPosition = position
261
-
262
- // 如果地图已加载,立即应用;否则缓存等待地图加载完成
263
- if (isMapLoaded) {
264
- mainHandler.post {
265
- applyInitialCameraPosition(position)
266
- }
267
- } else {
268
- pendingCameraPosition = position
269
- }
270
- }
271
-
272
- /**
273
- * 实际应用相机位置
274
- * @param position 相机位置配置
275
- */
276
- private fun applyInitialCameraPosition(position: Map<String, Any?>) {
277
- cameraManager.setInitialCameraPosition(position)
278
- }
279
-
280
- // ==================== UI 控件和手势 ====================
281
-
282
- /** 设置是否显示缩放控件 */
283
- fun setShowsZoomControls(show: Boolean) = uiManager.setShowsZoomControls(show)
284
- /** 设置是否显示指南针 */
285
- fun setShowsCompass(show: Boolean) = uiManager.setShowsCompass(show)
286
- /** 设置是否显示比例尺 */
287
- fun setShowsScale(show: Boolean) = uiManager.setShowsScale(show)
288
-
289
- /** 设置是否启用缩放手势 */
290
- fun setZoomEnabled(enabled: Boolean) = uiManager.setZoomEnabled(enabled)
291
- /** 设置是否启用滚动手势 */
292
- fun setScrollEnabled(enabled: Boolean) = uiManager.setScrollEnabled(enabled)
293
- /** 设置是否启用旋转手势 */
294
- fun setRotateEnabled(enabled: Boolean) = uiManager.setRotateEnabled(enabled)
295
- /** 设置是否启用倾斜手势 */
296
- fun setTiltEnabled(enabled: Boolean) = uiManager.setTiltEnabled(enabled)
297
-
298
- /** 设置最大缩放级别 */
299
- fun setMaxZoom(maxZoom: Float) = cameraManager.setMaxZoomLevel(maxZoom)
300
- /** 设置最小缩放级别 */
301
- fun setMinZoom(minZoom: Float) = cameraManager.setMinZoomLevel(minZoom)
302
-
303
- /** 设置是否显示用户位置 */
304
- fun setShowsUserLocation(show: Boolean) {
305
- mainHandler.post {
306
- uiManager.setShowsUserLocation(show, followUserLocation)
307
- }
308
- }
309
-
310
- /**
311
- * 设置是否跟随用户位置
312
- * @param follow 是否跟随
313
- */
314
- fun setFollowUserLocation(follow: Boolean) {
315
- followUserLocation = follow
316
- // 如果定位已开启,立即应用新设置
317
- mainHandler.post {
318
- if (aMap.isMyLocationEnabled) {
319
- uiManager.setShowsUserLocation(true, follow)
320
- }
321
- }
322
- }
323
-
324
- /**
325
- * 设置用户位置样式
326
- * @param representation 样式配置
327
- */
328
- fun setUserLocationRepresentation(representation: Map<String, Any>) {
329
- uiManager.setUserLocationRepresentation(representation)
330
- }
331
-
332
- /** 设置是否显示交通路况 */
333
- fun setShowsTraffic(show: Boolean) = uiManager.setShowsTraffic(show)
334
- /** 设置是否显示建筑物 */
335
- fun setShowsBuildings(show: Boolean) = uiManager.setShowsBuildings(show)
336
- /** 设置是否显示室内地图 */
337
- fun setShowsIndoorMap(show: Boolean) = uiManager.setShowsIndoorMap(show)
338
-
339
- // ==================== 相机控制方法 ====================
340
-
341
- /**
342
- * 移动相机
343
- * @param position 目标位置
344
- * @param duration 动画时长(毫秒)
345
- */
346
- fun moveCamera(position: Map<String, Any>, duration: Int) {
347
- cameraManager.moveCamera(position, duration)
348
- }
349
-
350
- /**
351
- * 获取屏幕坐标对应的地理坐标
352
- * @param point 屏幕坐标
353
- * @return 地理坐标
354
- */
355
- fun getLatLng(point: Map<String, Double>): Map<String, Double> {
356
- return cameraManager.getLatLng(point)
357
- }
358
-
359
- /**
360
- * 设置地图中心点
361
- * @param center 中心点坐标
362
- * @param animated 是否动画
363
- */
364
- fun setCenter(center: Map<String, Double>, animated: Boolean) {
365
- cameraManager.setCenter(center, animated)
366
- }
367
-
368
- /**
369
- * 设置地图缩放级别
370
- * @param zoom 缩放级别
371
- * @param animated 是否动画
372
- */
373
- fun setZoomLevel(zoom: Float, animated: Boolean) {
374
- cameraManager.setZoomLevel(zoom, animated)
375
- }
376
-
377
- /**
378
- * 获取当前相机位置
379
- * @return 相机位置信息
380
- */
381
- fun getCameraPosition(): Map<String, Any> {
382
- return cameraManager.getCameraPosition()
383
- }
384
-
385
- // ==================== 覆盖物管理 ====================
386
-
387
- /** 添加圆形覆盖物 */
388
- fun addCircle(id: String, props: Map<String, Any>) {
389
- mainHandler.post {
390
- overlayManager.addCircle(id, props)
391
- }
392
- }
393
-
394
- /** 移除圆形覆盖物 */
395
- fun removeCircle(id: String) {
396
- mainHandler.post {
397
- overlayManager.removeCircle(id)
398
- }
399
- }
400
-
401
- /** 更新圆形覆盖物 */
402
- fun updateCircle(id: String, props: Map<String, Any>) {
403
- mainHandler.post {
404
- overlayManager.updateCircle(id, props)
405
- }
406
- }
407
-
408
- /** 添加标记点 */
409
- fun addMarker(id: String, props: Map<String, Any>) {
410
- mainHandler.post {
411
- overlayManager.addMarker(id, props)
412
- }
413
- }
414
-
415
- /** 移除标记点 */
416
- fun removeMarker(id: String) {
417
- mainHandler.post {
418
- overlayManager.removeMarker(id)
419
- }
420
- }
421
-
422
- /** 更新标记点 */
423
- fun updateMarker(id: String, props: Map<String, Any>) {
424
- mainHandler.post {
425
- overlayManager.updateMarker(id, props)
426
- }
427
- }
428
-
429
- /** 添加折线 */
430
- fun addPolyline(id: String, props: Map<String, Any>) {
431
- mainHandler.post {
432
- overlayManager.addPolyline(id, props)
433
- }
434
- }
435
-
436
- /** 移除折线 */
437
- fun removePolyline(id: String) {
438
- mainHandler.post {
439
- overlayManager.removePolyline(id)
440
- }
441
- }
442
-
443
- /** 更新折线 */
444
- fun updatePolyline(id: String, props: Map<String, Any>) {
445
- mainHandler.post {
446
- overlayManager.updatePolyline(id, props)
447
- }
448
- }
449
-
450
- /** 添加多边形 */
451
- fun addPolygon(id: String, props: Map<String, Any>) {
452
- mainHandler.post {
453
- overlayManager.addPolygon(id, props)
454
- }
455
- }
456
-
457
- /** 移除多边形 */
458
- fun removePolygon(id: String) {
459
- mainHandler.post {
460
- overlayManager.removePolygon(id)
461
- }
462
- }
463
-
464
- /** 更新多边形 */
465
- fun updatePolygon(id: String, props: Map<String, Any>) {
466
- mainHandler.post {
467
- overlayManager.updatePolygon(id, props)
468
- }
469
- }
470
-
471
- // ==================== 生命周期方法 ====================
472
-
473
- /** 恢复地图 */
474
- @Suppress("unused")
475
- fun onResume() {
476
- mapView.onResume()
477
- }
478
-
479
- /** 暂停地图 */
480
- @Suppress("unused")
481
- fun onPause() {
482
- mapView.onPause()
483
- }
484
-
485
- /** 销毁地图 */
486
- @Suppress("unused")
487
- fun onDestroy() {
488
- // 清理 Handler 回调,防止内存泄露
489
- mainHandler.removeCallbacksAndMessages(null)
490
-
491
- // 清理地图监听器
492
- aMap.setOnMapClickListener(null)
493
- aMap.setOnMapLongClickListener(null)
494
- aMap.setOnMapLoadedListener(null)
495
-
496
- // 清理覆盖物
497
- overlayManager.clear()
498
-
499
- // 清理 MarkerView 列表
500
- markerViews.clear()
501
-
502
- // 销毁地图
503
- mapView.onDestroy()
504
- }
505
-
506
- /** 保存实例状态 */
507
- @Suppress("unused")
508
- fun onSaveInstanceState(outState: android.os.Bundle) {
509
- mapView.onSaveInstanceState(outState)
510
- }
511
-
512
- /**
513
- * 存储 MarkerView 引用,因为它们不在视图层级中
514
- */
515
- private val markerViews = mutableListOf<MarkerView>()
516
-
517
- /**
518
- * 添加子视图时自动连接到地图
519
- *
520
- * 关键修复:MarkerView 不进入视图层级,但要确保 React Native 追踪正确
521
- */
522
- override fun addView(child: View?, index: Int) {
523
- if (child is MarkerView) {
524
- // ✅ MarkerView 不加入视图层级
525
- child.setMap(aMap)
526
- markerViews.add(child)
527
- Log.d(TAG, "✅ MarkerView 已添加到特殊列表(不在视图层级中),数量: ${markerViews.size}")
528
- // ⚠️ 不调用 super.addView,所以 React Native 不会追踪它
529
- return
530
- }
531
-
532
- // ⚠️ 如果是 MapView 本身,必须添加
533
- if (child is com.amap.api.maps.MapView) {
534
- super.addView(child, index)
535
- Log.d(TAG, "✅ MapView 已添加")
536
- return
537
- }
538
-
539
- super.addView(child, index)
540
-
541
- // 自动将地图实例传递给其他覆盖物子视图
542
- child?.let {
543
- when (it) {
544
- is PolylineView -> it.setMap(aMap)
545
- is PolygonView -> it.setMap(aMap)
546
- is CircleView -> it.setMap(aMap)
547
- is HeatMapView -> it.setMap(aMap)
548
- is MultiPointView -> it.setMap(aMap)
549
- is ClusterView -> it.setMap(aMap)
550
- }
551
- }
552
- }
553
-
554
- /**
555
- * 移除子视图
556
- */
557
- override fun removeView(child: View?) {
558
- if (child is MarkerView) {
559
- // 从 MarkerView 列表中移除并清理
560
- markerViews.remove(child)
561
- child.removeMarker()
562
- Log.d(TAG, "✅ MarkerView 已清理,当前 markerViews 数量: ${markerViews.size}")
563
- return
564
- }
565
-
566
- try {
567
- super.removeView(child)
568
- } catch (e: Exception) {
569
- Log.e(TAG, "removeView 异常", e)
570
- }
571
- }
572
-
573
- /**
574
- * 按索引移除视图
575
- *
576
- * 终极修复:完全忽略所有无效的移除请求
577
- */
578
- override fun removeViewAt(index: Int) {
579
- try {
580
- val actualChildCount = super.getChildCount()
581
-
582
- Log.d(TAG, "removeViewAt 调用: index=$index, super.childCount=$actualChildCount")
583
-
584
- // ✅ 如果没有子视图,直接返回
585
- if (actualChildCount == 0) {
586
- Log.w(TAG, "⚠️ 无子视图,忽略: index=$index")
587
- return
588
- }
589
-
590
- // ✅ 索引越界,直接返回
591
- if (index < 0 || index >= actualChildCount) {
592
- Log.w(TAG, "⚠️ 索引越界,忽略: index=$index, childCount=$actualChildCount")
593
- return
594
- }
595
-
596
- // 检查要移除的视图类型
597
- val child = super.getChildAt(index)
598
-
599
- // ❌ MapView 绝对不能移除
600
- if (child is com.amap.api.maps.MapView) {
601
- Log.e(TAG, "❌ 阻止移除 MapView!index=$index")
602
- // 不移除,直接返回
603
- return
604
- }
605
-
606
- // MarkerView 特殊处理(虽然理论上不应该出现在这里)
607
- if (child is MarkerView) {
608
- Log.d(TAG, "⚠️ 发现 MarkerView 在视图层级中,使用 removeView")
609
- removeView(child)
610
- return
611
- }
612
-
613
- // 正常移除其他视图
614
- super.removeViewAt(index)
615
- Log.d(TAG, "✅ 成功移除视图: index=$index")
616
-
617
- } catch (e: IllegalArgumentException) {
618
- // 索引异常,静默忽略
619
- Log.w(TAG, "⚠️ 索引异常,已忽略: ${e.message}")
620
- } catch (e: IndexOutOfBoundsException) {
621
- // 越界异常,静默忽略
622
- Log.w(TAG, "⚠️ 越界异常,已忽略: ${e.message}")
623
- } catch (e: Exception) {
624
- // 其他所有异常,静默忽略
625
- Log.e(TAG, "⚠️ 移除视图异常,已忽略", e)
626
- }
627
- }
628
-
629
- private fun checkDeclarativePolylinePress(latLng: LatLng): Boolean {
630
- for (i in 0 until childCount) {
631
- val child = getChildAt(i)
632
- if (child is PolylineView) {
633
- if (child.checkPress(latLng)) {
634
- return true
633
+ return false
634
+ }
635
+
636
+ private fun checkDeclarativeCirclePress(latLng: LatLng): Boolean {
637
+ for (i in 0 until childCount) {
638
+ val child = getChildAt(i)
639
+ if (child is CircleView) {
640
+ if (child.checkPress(latLng)) {
641
+ return true
642
+ }
643
+ }
635
644
  }
636
- }
637
- }
638
- return false
639
- }
640
-
641
- private fun checkDeclarativePolygonPress(latLng: LatLng): Boolean {
642
- for (i in 0 until childCount) {
643
- val child = getChildAt(i)
644
- if (child is PolygonView) {
645
- if (child.checkPress(latLng)) {
646
- return true
645
+ return false
646
+ }
647
+
648
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
649
+ super.onLayout(changed, left, top, right, bottom)
650
+ }
651
+
652
+ /**
653
+ * 重写 getChildCount,返回 React Native 期望的子视图数量
654
+ * 包括实际视图层级中的子视图 + MarkerView 数量
655
+ */
656
+ override fun getChildCount(): Int {
657
+ return super.getChildCount() + markerViews.size
658
+ }
659
+
660
+ /**
661
+ * 重写 getChildAt,处理 MarkerView 的索引映射
662
+ * 当索引超出实际子视图数量时,返回对应的 MarkerView
663
+ */
664
+ override fun getChildAt(index: Int): View? {
665
+ val actualCount = super.getChildCount()
666
+
667
+ if (index < actualCount) {
668
+ return super.getChildAt(index)
647
669
  }
648
- }
649
- }
650
- return false
651
- }
652
-
653
- private fun checkDeclarativeCirclePress(latLng: LatLng): Boolean {
654
- for (i in 0 until childCount) {
655
- val child = getChildAt(i)
656
- if (child is CircleView) {
657
- if (child.checkPress(latLng)) {
658
- return true
670
+
671
+ val markerIndex = index - actualCount
672
+ if (markerIndex >= 0 && markerIndex < markerViews.size) {
673
+ return markerViews[markerIndex]
659
674
  }
660
- }
661
- }
662
- return false
663
- }
664
-
665
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
666
- super.onLayout(changed, left, top, right, bottom)
667
- }
675
+
676
+ return null
677
+ }
668
678
  }