expo-gaode-map 2.2.41 → 2.2.42
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.
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +4 -31
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +55 -59
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerViewModule.kt +4 -0
- package/ios/ExpoGaodeMapView.swift +9 -29
- package/ios/overlays/MarkerView.swift +58 -85
- package/package.json +1 -1
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -217,7 +217,6 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
217
217
|
|
|
218
218
|
// 辅助监听器列表
|
|
219
219
|
private val cameraChangeListeners = mutableListOf<AMap.OnCameraChangeListener>()
|
|
220
|
-
private val pendingMarkerRemovalTasks = mutableMapOf<MarkerView, Runnable>()
|
|
221
220
|
|
|
222
221
|
fun addCameraChangeListener(listener: AMap.OnCameraChangeListener) {
|
|
223
222
|
if (!cameraChangeListeners.contains(listener)) {
|
|
@@ -651,7 +650,6 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
651
650
|
mainHandler.removeCallbacksAndMessages(null)
|
|
652
651
|
pendingCameraMoveData = null
|
|
653
652
|
pendingCameraMoveDispatch = null
|
|
654
|
-
pendingMarkerRemovalTasks.clear()
|
|
655
653
|
|
|
656
654
|
// 清理所有地图监听器
|
|
657
655
|
aMap.setOnMapClickListener(null)
|
|
@@ -757,7 +755,6 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
757
755
|
@SuppressLint("UseKtx")
|
|
758
756
|
override fun addView(child: View?, index: Int) {
|
|
759
757
|
if (child is MarkerView) {
|
|
760
|
-
cancelPendingMarkerRemoval(child)
|
|
761
758
|
child.setMap(aMap)
|
|
762
759
|
// MarkerView 需要保留可测量尺寸,否则 Android 无法正确处理
|
|
763
760
|
// Text / View 的 maxWidth 等布局约束,最终会被测成整行宽度。
|
|
@@ -789,13 +786,10 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
789
786
|
}
|
|
790
787
|
}
|
|
791
788
|
|
|
792
|
-
/**
|
|
793
|
-
* 移除子视图
|
|
794
|
-
* 延迟移除 Marker,让它们跟随地图一起延迟销毁
|
|
795
|
-
*/
|
|
789
|
+
/** 移除子视图,并同步移除对应的原生 Marker。 */
|
|
796
790
|
override fun removeView(child: View?) {
|
|
797
791
|
if (child is MarkerView) {
|
|
798
|
-
|
|
792
|
+
child.removeMarker()
|
|
799
793
|
super.removeView(child)
|
|
800
794
|
return
|
|
801
795
|
}
|
|
@@ -807,10 +801,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
807
801
|
}
|
|
808
802
|
}
|
|
809
803
|
|
|
810
|
-
/**
|
|
811
|
-
* 按索引移除视图
|
|
812
|
-
* 延迟移除 Marker,让它们跟随地图一起延迟销毁
|
|
813
|
-
*/
|
|
804
|
+
/** 按索引移除视图,并同步移除对应的原生 Marker。 */
|
|
814
805
|
override fun removeViewAt(index: Int) {
|
|
815
806
|
try {
|
|
816
807
|
val child = super.getChildAt(index)
|
|
@@ -820,7 +811,7 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
820
811
|
}
|
|
821
812
|
|
|
822
813
|
if (child is MarkerView) {
|
|
823
|
-
|
|
814
|
+
child.removeMarker()
|
|
824
815
|
}
|
|
825
816
|
|
|
826
817
|
super.removeViewAt(index)
|
|
@@ -830,24 +821,6 @@ class ExpoGaodeMapView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
830
821
|
}
|
|
831
822
|
}
|
|
832
823
|
|
|
833
|
-
private fun scheduleMarkerRemoval(markerView: MarkerView) {
|
|
834
|
-
cancelPendingMarkerRemoval(markerView)
|
|
835
|
-
|
|
836
|
-
val task = Runnable {
|
|
837
|
-
pendingMarkerRemovalTasks.remove(markerView)
|
|
838
|
-
if (markerView.parent == null) {
|
|
839
|
-
markerView.removeMarker()
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
pendingMarkerRemovalTasks[markerView] = task
|
|
844
|
-
mainHandler.postDelayed(task, 500)
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
private fun cancelPendingMarkerRemoval(markerView: MarkerView) {
|
|
848
|
-
pendingMarkerRemovalTasks.remove(markerView)?.let(mainHandler::removeCallbacks)
|
|
849
|
-
}
|
|
850
|
-
|
|
851
824
|
private fun checkDeclarativePolylinePress(latLng: LatLng): Boolean {
|
|
852
825
|
for (i in 0 until childCount) {
|
|
853
826
|
val child = getChildAt(i)
|
|
@@ -224,7 +224,7 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
224
224
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
225
225
|
private var isRemoving = false // 标记是否正在被移除
|
|
226
226
|
private var pendingMarkerIconUpdate: Runnable? = null
|
|
227
|
-
private var
|
|
227
|
+
private var contentInvalidationGeneration: Long = 0
|
|
228
228
|
private var lastAppliedCustomMarkerKey: String? = null
|
|
229
229
|
|
|
230
230
|
// 缓存属性,在 marker 创建前保存
|
|
@@ -338,14 +338,9 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
338
338
|
|
|
339
339
|
flushPendingShowIfNeeded()
|
|
340
340
|
|
|
341
|
-
//
|
|
342
|
-
// 使用 post 延迟到下一帧,确保 children 完成测量和布局
|
|
341
|
+
// 等待 children 在同一渲染帧内稳定后再生成一次快照。
|
|
343
342
|
if (isNotEmpty() && marker != null) {
|
|
344
|
-
|
|
345
|
-
if (!isRemoving && marker != null && isNotEmpty()) {
|
|
346
|
-
updateMarkerIcon()
|
|
347
|
-
}
|
|
348
|
-
}
|
|
343
|
+
markCustomMarkerContentDirty()
|
|
349
344
|
}
|
|
350
345
|
}
|
|
351
346
|
|
|
@@ -484,9 +479,9 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
484
479
|
cacheKey = key
|
|
485
480
|
if (isNotEmpty()) {
|
|
486
481
|
// cacheKey 通常和 selected/zoom level 同帧变化。先淘汰当前 key 可能存在的旧截图,
|
|
487
|
-
//
|
|
482
|
+
// 等待 children 完成当前帧的 props/layout 更新后再重绘。
|
|
488
483
|
invalidateCurrentSnapshotCache()
|
|
489
|
-
markCustomMarkerContentDirty(
|
|
484
|
+
markCustomMarkerContentDirty()
|
|
490
485
|
} else {
|
|
491
486
|
pendingIconUri?.let { iconUri ->
|
|
492
487
|
marker?.let { loadAndSetIcon(iconUri, it) }
|
|
@@ -981,29 +976,54 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
981
976
|
marker?.let { showMarker(it) }
|
|
982
977
|
}
|
|
983
978
|
|
|
984
|
-
private fun
|
|
979
|
+
private fun scheduleMarkerIconUpdateAfterContentSettles() {
|
|
985
980
|
if (isRemoving || marker == null || isEmpty()) {
|
|
986
981
|
return
|
|
987
982
|
}
|
|
988
983
|
|
|
989
|
-
pendingMarkerIconUpdate?.let {
|
|
990
|
-
val
|
|
984
|
+
pendingMarkerIconUpdate?.let { removeCallbacks(it) }
|
|
985
|
+
val scheduledGeneration = contentInvalidationGeneration
|
|
986
|
+
val settleTask = Runnable {
|
|
991
987
|
pendingMarkerIconUpdate = null
|
|
992
|
-
if (
|
|
993
|
-
|
|
988
|
+
if (isRemoving || marker == null || isEmpty()) {
|
|
989
|
+
return@Runnable
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (scheduledGeneration != contentInvalidationGeneration) {
|
|
993
|
+
scheduleMarkerIconUpdateAfterContentSettles()
|
|
994
|
+
return@Runnable
|
|
994
995
|
}
|
|
996
|
+
|
|
997
|
+
scheduleMarkerIconUpdateForStableGeneration(scheduledGeneration)
|
|
995
998
|
}
|
|
996
|
-
pendingMarkerIconUpdate =
|
|
999
|
+
pendingMarkerIconUpdate = settleTask
|
|
1000
|
+
postOnAnimation(settleTask)
|
|
1001
|
+
}
|
|
997
1002
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1003
|
+
private fun scheduleMarkerIconUpdateForStableGeneration(expectedGeneration: Long) {
|
|
1004
|
+
if (isRemoving || marker == null || isEmpty()) {
|
|
1005
|
+
return
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
val renderTask = Runnable {
|
|
1009
|
+
pendingMarkerIconUpdate = null
|
|
1010
|
+
if (isRemoving || marker == null || isEmpty()) {
|
|
1011
|
+
return@Runnable
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (expectedGeneration != contentInvalidationGeneration) {
|
|
1015
|
+
scheduleMarkerIconUpdateAfterContentSettles()
|
|
1016
|
+
return@Runnable
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
updateMarkerIcon()
|
|
1002
1020
|
}
|
|
1021
|
+
pendingMarkerIconUpdate = renderTask
|
|
1022
|
+
postOnAnimation(renderTask)
|
|
1003
1023
|
}
|
|
1004
1024
|
|
|
1005
1025
|
private fun cancelPendingMarkerIconUpdate() {
|
|
1006
|
-
pendingMarkerIconUpdate?.let {
|
|
1026
|
+
pendingMarkerIconUpdate?.let { removeCallbacks(it) }
|
|
1007
1027
|
pendingMarkerIconUpdate = null
|
|
1008
1028
|
}
|
|
1009
1029
|
|
|
@@ -1022,17 +1042,19 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1022
1042
|
IconBitmapCache.remove(key)
|
|
1023
1043
|
}
|
|
1024
1044
|
|
|
1025
|
-
private fun markCustomMarkerContentDirty(
|
|
1045
|
+
private fun markCustomMarkerContentDirty() {
|
|
1026
1046
|
if (isRemoving || isEmpty()) {
|
|
1027
1047
|
return
|
|
1028
1048
|
}
|
|
1029
1049
|
|
|
1050
|
+
contentInvalidationGeneration += 1
|
|
1051
|
+
|
|
1030
1052
|
// children(尤其是 Image)异步加载完成后,需要强制淘汰当前 marker 已应用的位图缓存,
|
|
1031
1053
|
// 否则同一个 cacheKey 会一直命中“占位态”的旧 bitmap,表现为灰块/蒙层不更新。
|
|
1032
1054
|
// 这里仅清理当前 marker 已应用的 key,不会影响其它 marker。
|
|
1033
1055
|
invalidateAppliedCustomMarkerCaches(clearGlobalCache = true)
|
|
1034
1056
|
if (marker != null) {
|
|
1035
|
-
|
|
1057
|
+
scheduleMarkerIconUpdateAfterContentSettles()
|
|
1036
1058
|
}
|
|
1037
1059
|
}
|
|
1038
1060
|
|
|
@@ -1043,8 +1065,8 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1043
1065
|
if (child != null && contains(child)) {
|
|
1044
1066
|
super.removeView(child)
|
|
1045
1067
|
// 不要在这里恢复默认图标
|
|
1046
|
-
// 如果 MarkerView
|
|
1047
|
-
//
|
|
1068
|
+
// 如果 MarkerView 整体被移除,父 MapView / OnViewDestroys 会清理原生 Marker。
|
|
1069
|
+
// 如果只是替换 children,后续 add/layout invalidation 会生成新快照。
|
|
1048
1070
|
}
|
|
1049
1071
|
} catch (_: Exception) {
|
|
1050
1072
|
// 忽略异常
|
|
@@ -1057,10 +1079,10 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1057
1079
|
super.removeViewAt(index)
|
|
1058
1080
|
// 只在还有子视图时更新图标
|
|
1059
1081
|
if (!isRemoving && isNotEmpty() && marker != null) {
|
|
1060
|
-
markCustomMarkerContentDirty(
|
|
1082
|
+
markCustomMarkerContentDirty()
|
|
1061
1083
|
}
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1084
|
+
// 最后一个子视图被移除时保留 last-good 快照;新 child 加入后原子替换,
|
|
1085
|
+
// MarkerView 真正销毁则由父 MapView / OnViewDestroys 负责清理。
|
|
1064
1086
|
}
|
|
1065
1087
|
} catch (_: Exception) {
|
|
1066
1088
|
// 忽略异常
|
|
@@ -1344,8 +1366,8 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1344
1366
|
* 移除标记
|
|
1345
1367
|
*/
|
|
1346
1368
|
fun removeMarker() {
|
|
1347
|
-
cancelPendingDetachRemoval()
|
|
1348
1369
|
cancelPendingMarkerIconUpdate()
|
|
1370
|
+
contentInvalidationGeneration += 1
|
|
1349
1371
|
|
|
1350
1372
|
// 停止平滑移动
|
|
1351
1373
|
stopSmoothMove()
|
|
@@ -1361,44 +1383,18 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1361
1383
|
|
|
1362
1384
|
override fun onAttachedToWindow() {
|
|
1363
1385
|
super.onAttachedToWindow()
|
|
1364
|
-
cancelPendingDetachRemoval()
|
|
1365
1386
|
isRemoving = false
|
|
1366
1387
|
|
|
1367
1388
|
if (isNotEmpty() && marker != null) {
|
|
1368
|
-
|
|
1389
|
+
markCustomMarkerContentDirty()
|
|
1369
1390
|
}
|
|
1370
1391
|
}
|
|
1371
1392
|
|
|
1372
1393
|
override fun onDetachedFromWindow() {
|
|
1373
1394
|
super.onDetachedFromWindow()
|
|
1374
|
-
|
|
1375
|
-
//
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
// 延迟检查 parent 状态。React 更新 children 时可能会短暂 detach/attach,
|
|
1379
|
-
// 过早删除会导致点击后其它 Marker 消失。
|
|
1380
|
-
val task = Runnable {
|
|
1381
|
-
pendingDetachRemovalTask = null
|
|
1382
|
-
if (parent == null) {
|
|
1383
|
-
// 标记正在移除
|
|
1384
|
-
isRemoving = true
|
|
1385
|
-
cancelPendingMarkerIconUpdate()
|
|
1386
|
-
|
|
1387
|
-
// 🔑 修复:不要清空全局缓存
|
|
1388
|
-
// 理由:会影响其他 Marker 的性能
|
|
1389
|
-
// 缓存应该由 LruCache 自动管理,或在合适的时机(如内存警告)统一清理
|
|
1390
|
-
|
|
1391
|
-
// 移除 marker
|
|
1392
|
-
removeMarker()
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
pendingDetachRemovalTask = task
|
|
1396
|
-
mainHandler.postDelayed(task, 500)
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
private fun cancelPendingDetachRemoval() {
|
|
1400
|
-
pendingDetachRemovalTask?.let { mainHandler.removeCallbacks(it) }
|
|
1401
|
-
pendingDetachRemovalTask = null
|
|
1395
|
+
// Window detach 可能只是页面转场或 Fabric 重挂载,不代表 Marker 已销毁。
|
|
1396
|
+
// 父 MapView 的 removeView/removeViewAt 与 OnViewDestroys 才负责真实清理。
|
|
1397
|
+
cancelPendingMarkerIconUpdate()
|
|
1402
1398
|
}
|
|
1403
1399
|
|
|
1404
1400
|
}
|
|
@@ -12,6 +12,10 @@ class MarkerViewModule : Module() {
|
|
|
12
12
|
|
|
13
13
|
View(MarkerView::class) {
|
|
14
14
|
Events("onMarkerPress", "onMarkerDragStart", "onMarkerDrag", "onMarkerDragEnd")
|
|
15
|
+
|
|
16
|
+
OnViewDestroys { view: MarkerView ->
|
|
17
|
+
view.removeMarker()
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
// 纬度
|
|
17
21
|
Prop<Double>("latitude") { view, lat ->
|
|
@@ -114,8 +114,6 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
114
114
|
private var overlayContainer: UIView!
|
|
115
115
|
/// 显式跟踪所有覆盖物视图(新架构下 subviews 可能不可靠)
|
|
116
116
|
private var overlayViews: [UIView] = []
|
|
117
|
-
/// MarkerView 短暂 detach 时延迟注销,避免 annotation 暂时找不到自定义 view 而回退默认样式
|
|
118
|
-
private var pendingMarkerOverlayUnregisterTasks: [ObjectIdentifier: DispatchWorkItem] = [:]
|
|
119
117
|
|
|
120
118
|
// MARK: - 事件节流控制
|
|
121
119
|
|
|
@@ -253,7 +251,11 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
253
251
|
}
|
|
254
252
|
|
|
255
253
|
private func registerOverlayView(_ view: UIView) {
|
|
256
|
-
|
|
254
|
+
if let markerView = view as? MarkerView {
|
|
255
|
+
markerView.onPermanentDetach = { [weak self] detachedMarkerView in
|
|
256
|
+
self?.unregisterOverlayView(detachedMarkerView)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
257
259
|
|
|
258
260
|
guard !overlayViews.contains(where: { $0 === view }) else {
|
|
259
261
|
return
|
|
@@ -262,29 +264,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
262
264
|
}
|
|
263
265
|
|
|
264
266
|
private func unregisterOverlayView(_ view: UIView) {
|
|
265
|
-
cancelPendingMarkerOverlayUnregister(for: view)
|
|
266
267
|
overlayViews.removeAll { $0 === view }
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
private func scheduleMarkerOverlayUnregister(_ markerView: MarkerView) {
|
|
270
|
-
cancelPendingMarkerOverlayUnregister(for: markerView)
|
|
271
|
-
|
|
272
|
-
let identifier = ObjectIdentifier(markerView)
|
|
273
|
-
let task = DispatchWorkItem { [weak self, weak markerView] in
|
|
274
|
-
guard let self = self, let markerView = markerView else { return }
|
|
275
|
-
self.pendingMarkerOverlayUnregisterTasks[identifier] = nil
|
|
276
|
-
guard markerView.superview == nil else { return }
|
|
277
|
-
self.unregisterOverlayView(markerView)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
pendingMarkerOverlayUnregisterTasks[identifier] = task
|
|
281
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: task)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private func cancelPendingMarkerOverlayUnregister(for view: UIView) {
|
|
285
|
-
let identifier = ObjectIdentifier(view)
|
|
286
|
-
pendingMarkerOverlayUnregisterTasks[identifier]?.cancel()
|
|
287
|
-
pendingMarkerOverlayUnregisterTasks[identifier] = nil
|
|
268
|
+
(view as? MarkerView)?.onPermanentDetach = nil
|
|
288
269
|
}
|
|
289
270
|
|
|
290
271
|
private func prepareOverlayViewForHosting(_ view: UIView) {
|
|
@@ -362,8 +343,9 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
362
343
|
// 🔑 处理所有覆盖物 - 从跟踪数组中移除并确保 native 对象也从地图移除
|
|
363
344
|
// 🔑 关键修复:先从数组移除,再调用 super,防止 super 触发的事件回调中引用已卸载的视图
|
|
364
345
|
if let markerView = subview as? MarkerView {
|
|
365
|
-
|
|
366
|
-
// MarkerView
|
|
346
|
+
// 立即解除强引用。短暂重挂载会在 didAddSubview 中重新注册,
|
|
347
|
+
// 真正卸载则允许 MarkerView deinit 及时清理 annotation。
|
|
348
|
+
unregisterOverlayView(markerView)
|
|
367
349
|
} else if let circleView = subview as? CircleView {
|
|
368
350
|
unregisterOverlayView(circleView)
|
|
369
351
|
if let mapView, let circle = circleView.circle {
|
|
@@ -924,8 +906,6 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
|
|
|
924
906
|
appleMapView?.removeOverlays(appleMapView?.overlays ?? [])
|
|
925
907
|
|
|
926
908
|
// 清空覆盖物数组
|
|
927
|
-
pendingMarkerOverlayUnregisterTasks.values.forEach { $0.cancel() }
|
|
928
|
-
pendingMarkerOverlayUnregisterTasks.removeAll()
|
|
929
909
|
overlayViews.removeAll()
|
|
930
910
|
|
|
931
911
|
// 移除所有子视图
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
2
|
import MAMapKit
|
|
3
|
+
import QuartzCore
|
|
3
4
|
import UIKit
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -58,6 +59,8 @@ class MarkerView: ExpoView {
|
|
|
58
59
|
var cacheKey: String?
|
|
59
60
|
/// 标记是否正在被移除(防止重复移除)
|
|
60
61
|
private var isRemoving: Bool = false
|
|
62
|
+
/// 旧架构物理移除完成后通知 Map 容器解除 overlay 强引用
|
|
63
|
+
var onPermanentDetach: ((MarkerView) -> Void)?
|
|
61
64
|
|
|
62
65
|
// 平滑移动相关
|
|
63
66
|
var smoothMovePath: [[String: Double]] = []
|
|
@@ -75,12 +78,8 @@ class MarkerView: ExpoView {
|
|
|
75
78
|
private var pendingUpdateTask: DispatchWorkItem?
|
|
76
79
|
/// 子视图变化后的延迟刷新任务
|
|
77
80
|
private var pendingSubviewRefreshTask: DispatchWorkItem?
|
|
78
|
-
///
|
|
79
|
-
private var
|
|
80
|
-
/// 视图 detach 后延迟确认移除 annotation,避免 React 更新时短暂 detach 导致闪烁
|
|
81
|
-
private var pendingAnnotationRemovalTask: DispatchWorkItem?
|
|
82
|
-
/// cacheKey 刷新代次,用于丢弃过期截图任务
|
|
83
|
-
private var cacheKeyRefreshGeneration: Int = 0
|
|
81
|
+
/// 下一次主队列确认物理 detach 是否仍然成立
|
|
82
|
+
private var pendingDetachCheckTask: DispatchWorkItem?
|
|
84
83
|
/// 最近一次应用到 annotationView 的 children 结构签名
|
|
85
84
|
private var lastRenderedChildrenSignature: String?
|
|
86
85
|
/// 最近一次成功渲染的 children 图片,用于缓存未命中时避免闪烁
|
|
@@ -113,7 +112,19 @@ class MarkerView: ExpoView {
|
|
|
113
112
|
// 始终返回 false,表示点击不在此视图内
|
|
114
113
|
return false
|
|
115
114
|
}
|
|
116
|
-
|
|
115
|
+
|
|
116
|
+
override func layoutSubviews() {
|
|
117
|
+
super.layoutSubviews()
|
|
118
|
+
|
|
119
|
+
guard !isRemoving,
|
|
120
|
+
!subviews.isEmpty,
|
|
121
|
+
currentChildrenAnnotationView() != nil else {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
scheduleChildrenImageRefresh(invalidateChildrenCache: true)
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
/**
|
|
118
129
|
* 检查地图是否已连接
|
|
119
130
|
*/
|
|
@@ -165,7 +176,7 @@ class MarkerView: ExpoView {
|
|
|
165
176
|
guard cacheKey != key else { return }
|
|
166
177
|
self.cacheKey = key
|
|
167
178
|
if !subviews.isEmpty {
|
|
168
|
-
|
|
179
|
+
scheduleChildrenImageRefresh(invalidateChildrenCache: true)
|
|
169
180
|
} else {
|
|
170
181
|
refreshAnnotationAppearance()
|
|
171
182
|
}
|
|
@@ -365,7 +376,7 @@ class MarkerView: ExpoView {
|
|
|
365
376
|
}
|
|
366
377
|
|
|
367
378
|
// 图片内容可能尚未就绪,保留旧图并延迟重试,避免透明闪烁。
|
|
368
|
-
DispatchQueue.main.
|
|
379
|
+
DispatchQueue.main.async { [weak self, weak annotationView] in
|
|
369
380
|
guard let self = self, let annotationView = annotationView else { return }
|
|
370
381
|
guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
|
|
371
382
|
// 再次检查缓存(避免重复渲染)
|
|
@@ -533,11 +544,9 @@ class MarkerView: ExpoView {
|
|
|
533
544
|
// 强制子视图使用指定尺寸布局
|
|
534
545
|
firstSubview.frame = CGRect(origin: .zero, size: size)
|
|
535
546
|
|
|
536
|
-
//
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.01))
|
|
540
|
-
}
|
|
547
|
+
// 在 React Native 完成当前挂载批次后执行一次确定性布局。
|
|
548
|
+
// 后续布局变化会再次合并触发快照,不需要阻塞主 RunLoop 猜测内容何时就绪。
|
|
549
|
+
forceLayoutRecursively(view: firstSubview)
|
|
541
550
|
|
|
542
551
|
if containsPendingImageContent(in: firstSubview) {
|
|
543
552
|
return nil
|
|
@@ -567,10 +576,16 @@ class MarkerView: ExpoView {
|
|
|
567
576
|
}
|
|
568
577
|
|
|
569
578
|
private func applyChildrenImage(_ image: UIImage, to annotationView: MAAnnotationView, signature: String? = nil) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
579
|
+
CATransaction.begin()
|
|
580
|
+
CATransaction.setDisableActions(true)
|
|
581
|
+
UIView.performWithoutAnimation {
|
|
582
|
+
annotationView.image = image
|
|
583
|
+
applyCenterOffset(to: annotationView, defaultOffset: .zero)
|
|
584
|
+
annotationView.canShowCallout = false
|
|
585
|
+
annotationView.isDraggable = draggable
|
|
586
|
+
annotationView.layoutIfNeeded()
|
|
587
|
+
}
|
|
588
|
+
CATransaction.commit()
|
|
574
589
|
lastRenderedChildrenImage = image
|
|
575
590
|
if let resolvedSignature = signature {
|
|
576
591
|
lastRenderedChildrenSignature = resolvedSignature
|
|
@@ -645,8 +660,7 @@ class MarkerView: ExpoView {
|
|
|
645
660
|
|
|
646
661
|
private func scheduleChildrenImageRefresh(
|
|
647
662
|
invalidateChildrenCache: Bool = false,
|
|
648
|
-
cacheImage: Bool = true
|
|
649
|
-
delay: TimeInterval = 0.02
|
|
663
|
+
cacheImage: Bool = true
|
|
650
664
|
) {
|
|
651
665
|
pendingSubviewRefreshTask?.cancel()
|
|
652
666
|
|
|
@@ -655,35 +669,7 @@ class MarkerView: ExpoView {
|
|
|
655
669
|
}
|
|
656
670
|
|
|
657
671
|
pendingSubviewRefreshTask = task
|
|
658
|
-
DispatchQueue.main.
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
private func scheduleCacheKeyChildrenImageRefresh() {
|
|
662
|
-
pendingSubviewRefreshTask?.cancel()
|
|
663
|
-
pendingSubviewRefreshTask = nil
|
|
664
|
-
pendingCacheKeyRefreshTask?.cancel()
|
|
665
|
-
|
|
666
|
-
cacheKeyRefreshGeneration += 1
|
|
667
|
-
let generation = cacheKeyRefreshGeneration
|
|
668
|
-
|
|
669
|
-
lastRenderedChildrenSignature = nil
|
|
670
|
-
invalidateCurrentChildrenCache()
|
|
671
|
-
|
|
672
|
-
let uncachedTask = DispatchWorkItem { [weak self] in
|
|
673
|
-
guard let self = self, !self.isRemoving, self.cacheKeyRefreshGeneration == generation else { return }
|
|
674
|
-
self.refreshChildrenImageInPlace(invalidateChildrenCache: true, cacheImage: false)
|
|
675
|
-
|
|
676
|
-
let cachedTask = DispatchWorkItem { [weak self] in
|
|
677
|
-
guard let self = self, !self.isRemoving, self.cacheKeyRefreshGeneration == generation else { return }
|
|
678
|
-
self.refreshChildrenImageInPlace(invalidateChildrenCache: true, cacheImage: true)
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
self.pendingCacheKeyRefreshTask = cachedTask
|
|
682
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.08, execute: cachedTask)
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
pendingCacheKeyRefreshTask = uncachedTask
|
|
686
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.02, execute: uncachedTask)
|
|
672
|
+
DispatchQueue.main.async(execute: task)
|
|
687
673
|
}
|
|
688
674
|
|
|
689
675
|
private func iconCacheKey(for iconUri: String) -> String {
|
|
@@ -849,18 +835,14 @@ class MarkerView: ExpoView {
|
|
|
849
835
|
forceLayoutRecursively(view: subview)
|
|
850
836
|
}
|
|
851
837
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
/**
|
|
855
|
-
* 当视图即将从父视图移除时调用
|
|
856
|
-
*/
|
|
838
|
+
|
|
857
839
|
override func willMove(toSuperview newSuperview: UIView?) {
|
|
858
840
|
super.willMove(toSuperview: newSuperview)
|
|
859
841
|
|
|
860
842
|
if newSuperview == nil {
|
|
861
|
-
|
|
843
|
+
scheduleDetachCheck()
|
|
862
844
|
} else {
|
|
863
|
-
|
|
845
|
+
cancelPendingDetachCheck()
|
|
864
846
|
}
|
|
865
847
|
}
|
|
866
848
|
|
|
@@ -868,47 +850,39 @@ class MarkerView: ExpoView {
|
|
|
868
850
|
super.didMoveToSuperview()
|
|
869
851
|
|
|
870
852
|
if superview != nil {
|
|
871
|
-
|
|
853
|
+
cancelPendingDetachCheck()
|
|
872
854
|
}
|
|
873
855
|
}
|
|
874
856
|
|
|
875
|
-
private func
|
|
876
|
-
|
|
857
|
+
private func scheduleDetachCheck() {
|
|
858
|
+
cancelPendingDetachCheck()
|
|
877
859
|
|
|
878
860
|
let task = DispatchWorkItem { [weak self] in
|
|
879
|
-
guard let self = self
|
|
880
|
-
self.
|
|
861
|
+
guard let self = self else { return }
|
|
862
|
+
self.pendingDetachCheckTask = nil
|
|
863
|
+
guard self.superview == nil else { return }
|
|
881
864
|
self.removeAnnotationFromMap()
|
|
882
865
|
}
|
|
883
866
|
|
|
884
|
-
|
|
885
|
-
DispatchQueue.main.
|
|
867
|
+
pendingDetachCheckTask = task
|
|
868
|
+
DispatchQueue.main.async(execute: task)
|
|
886
869
|
}
|
|
887
870
|
|
|
888
|
-
private func
|
|
889
|
-
|
|
890
|
-
|
|
871
|
+
private func cancelPendingDetachCheck() {
|
|
872
|
+
pendingDetachCheckTask?.cancel()
|
|
873
|
+
pendingDetachCheckTask = nil
|
|
891
874
|
}
|
|
892
|
-
|
|
893
|
-
/**
|
|
894
|
-
* 从地图移除标记点
|
|
895
|
-
*/
|
|
875
|
+
|
|
896
876
|
private func removeAnnotationFromMap() {
|
|
897
877
|
guard !isRemoving else { return }
|
|
898
878
|
isRemoving = true
|
|
899
|
-
|
|
879
|
+
cancelPendingDetachCheck()
|
|
900
880
|
pendingAddTask?.cancel(); pendingAddTask = nil
|
|
901
881
|
pendingUpdateTask?.cancel(); pendingUpdateTask = nil
|
|
902
882
|
pendingSubviewRefreshTask?.cancel(); pendingSubviewRefreshTask = nil
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
cleanupAnnotationFromMap()
|
|
907
|
-
} else {
|
|
908
|
-
DispatchQueue.main.sync {
|
|
909
|
-
cleanupAnnotationFromMap()
|
|
910
|
-
}
|
|
911
|
-
}
|
|
883
|
+
cleanupAnnotationFromMap()
|
|
884
|
+
onPermanentDetach?(self)
|
|
885
|
+
onPermanentDetach = nil
|
|
912
886
|
}
|
|
913
887
|
|
|
914
888
|
private func cleanupAnnotationFromMap() {
|
|
@@ -975,7 +949,7 @@ class MarkerView: ExpoView {
|
|
|
975
949
|
}
|
|
976
950
|
|
|
977
951
|
pendingSubviewRefreshTask = task
|
|
978
|
-
DispatchQueue.main.
|
|
952
|
+
DispatchQueue.main.async(execute: task)
|
|
979
953
|
}
|
|
980
954
|
|
|
981
955
|
private func refreshAnnotationForSubviewChanges(allowFallbackToDefault: Bool) {
|
|
@@ -1438,16 +1412,15 @@ class MarkerView: ExpoView {
|
|
|
1438
1412
|
}
|
|
1439
1413
|
|
|
1440
1414
|
/**
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1415
|
+
* MarkerView 从 React Native 注册表释放时清理原生 annotation。
|
|
1416
|
+
* Map 容器会在移除子视图时立即解除 overlayViews 的强引用。
|
|
1443
1417
|
*/
|
|
1444
1418
|
deinit {
|
|
1445
1419
|
// 取消待处理的任务
|
|
1446
1420
|
pendingAddTask?.cancel()
|
|
1447
1421
|
pendingUpdateTask?.cancel()
|
|
1448
1422
|
pendingSubviewRefreshTask?.cancel()
|
|
1449
|
-
|
|
1450
|
-
pendingAnnotationRemovalTask?.cancel()
|
|
1423
|
+
pendingDetachCheckTask?.cancel()
|
|
1451
1424
|
|
|
1452
1425
|
if Thread.isMainThread {
|
|
1453
1426
|
cleanupAnnotationFromMap()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-gaode-map",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.42",
|
|
4
4
|
"description": "Expo Modules AMap (Gaode Map) stack for Expo/React Native: Config Plugin, New Architecture support, maps, location, search, offline maps, and react-native-amap3d migration guidance.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|