expo-gaode-map-navigation 2.0.12-next.0 → 2.0.13
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/README.md +296 -7
- package/android/build.gradle +12 -4
- package/android/src/main/AndroidManifest.xml +10 -1
- package/android/src/main/cpp/cluster_jni.cpp +56 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +49 -8
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +83 -15
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +13 -3
- package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +36 -39
- package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +23 -17
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +5 -2
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +122 -10
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +37 -25
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +6 -6
- package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +103 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +1408 -394
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +121 -1
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +14 -28
- package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +28 -3
- package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +114 -15
- package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +24 -35
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +50 -36
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +21 -12
- package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
- package/android/src/main/res/drawable/landback_0.png +0 -0
- package/android/src/main/res/drawable/landback_1.png +0 -0
- package/android/src/main/res/drawable/landback_2.png +0 -0
- package/android/src/main/res/drawable/landback_3.png +0 -0
- package/android/src/main/res/drawable/landback_4.png +0 -0
- package/android/src/main/res/drawable/landback_5.png +0 -0
- package/android/src/main/res/drawable/landback_6.png +0 -0
- package/android/src/main/res/drawable/landback_7.png +0 -0
- package/android/src/main/res/drawable/landback_8.png +0 -0
- package/android/src/main/res/drawable/landback_9.png +0 -0
- package/android/src/main/res/drawable/landback_a.png +0 -0
- package/android/src/main/res/drawable/landback_b.png +0 -0
- package/android/src/main/res/drawable/landback_c.png +0 -0
- package/android/src/main/res/drawable/landback_d.png +0 -0
- package/android/src/main/res/drawable/landback_e.png +0 -0
- package/android/src/main/res/drawable/landback_f.png +0 -0
- package/android/src/main/res/drawable/landback_g.png +0 -0
- package/android/src/main/res/drawable/landback_h.png +0 -0
- package/android/src/main/res/drawable/landback_i.png +0 -0
- package/android/src/main/res/drawable/landback_j.png +0 -0
- package/android/src/main/res/drawable/landback_k.png +0 -0
- package/android/src/main/res/drawable/landback_l.png +0 -0
- package/android/src/main/res/drawable/landfront_0.png +0 -0
- package/android/src/main/res/drawable/landfront_00.png +0 -0
- package/android/src/main/res/drawable/landfront_1.png +0 -0
- package/android/src/main/res/drawable/landfront_11.png +0 -0
- package/android/src/main/res/drawable/landfront_20.png +0 -0
- package/android/src/main/res/drawable/landfront_21.png +0 -0
- package/android/src/main/res/drawable/landfront_22.png +0 -0
- package/android/src/main/res/drawable/landfront_3.png +0 -0
- package/android/src/main/res/drawable/landfront_33.png +0 -0
- package/android/src/main/res/drawable/landfront_40.png +0 -0
- package/android/src/main/res/drawable/landfront_43.png +0 -0
- package/android/src/main/res/drawable/landfront_44.png +0 -0
- package/android/src/main/res/drawable/landfront_5.png +0 -0
- package/android/src/main/res/drawable/landfront_55.png +0 -0
- package/android/src/main/res/drawable/landfront_61.png +0 -0
- package/android/src/main/res/drawable/landfront_63.png +0 -0
- package/android/src/main/res/drawable/landfront_66.png +0 -0
- package/android/src/main/res/drawable/landfront_70.png +0 -0
- package/android/src/main/res/drawable/landfront_71.png +0 -0
- package/android/src/main/res/drawable/landfront_73.png +0 -0
- package/android/src/main/res/drawable/landfront_77.png +0 -0
- package/android/src/main/res/drawable/landfront_8.png +0 -0
- package/android/src/main/res/drawable/landfront_88.png +0 -0
- package/android/src/main/res/drawable/landfront_90.png +0 -0
- package/android/src/main/res/drawable/landfront_95.png +0 -0
- package/android/src/main/res/drawable/landfront_99.png +0 -0
- package/android/src/main/res/drawable/landfront_a0.png +0 -0
- package/android/src/main/res/drawable/landfront_a8.png +0 -0
- package/android/src/main/res/drawable/landfront_aa.png +0 -0
- package/android/src/main/res/drawable/landfront_b1.png +0 -0
- package/android/src/main/res/drawable/landfront_b5.png +0 -0
- package/android/src/main/res/drawable/landfront_bb.png +0 -0
- package/android/src/main/res/drawable/landfront_c3.png +0 -0
- package/android/src/main/res/drawable/landfront_c8.png +0 -0
- package/android/src/main/res/drawable/landfront_cc.png +0 -0
- package/android/src/main/res/drawable/landfront_d.png +0 -0
- package/android/src/main/res/drawable/landfront_dd.png +0 -0
- package/android/src/main/res/drawable/landfront_e1.png +0 -0
- package/android/src/main/res/drawable/landfront_e5.png +0 -0
- package/android/src/main/res/drawable/landfront_ee.png +0 -0
- package/android/src/main/res/drawable/landfront_f0.png +0 -0
- package/android/src/main/res/drawable/landfront_f1.png +0 -0
- package/android/src/main/res/drawable/landfront_f5.png +0 -0
- package/android/src/main/res/drawable/landfront_ff.png +0 -0
- package/android/src/main/res/drawable/landfront_g3.png +0 -0
- package/android/src/main/res/drawable/landfront_g5.png +0 -0
- package/android/src/main/res/drawable/landfront_gg.png +0 -0
- package/android/src/main/res/drawable/landfront_h1.png +0 -0
- package/android/src/main/res/drawable/landfront_h3.png +0 -0
- package/android/src/main/res/drawable/landfront_h5.png +0 -0
- package/android/src/main/res/drawable/landfront_hh.png +0 -0
- package/android/src/main/res/drawable/landfront_i0.png +0 -0
- package/android/src/main/res/drawable/landfront_i3.png +0 -0
- package/android/src/main/res/drawable/landfront_i5.png +0 -0
- package/android/src/main/res/drawable/landfront_ii.png +0 -0
- package/android/src/main/res/drawable/landfront_j1.png +0 -0
- package/android/src/main/res/drawable/landfront_j8.png +0 -0
- package/android/src/main/res/drawable/landfront_jj.png +0 -0
- package/android/src/main/res/drawable/landfront_kk.png +0 -0
- package/android/src/main/res/drawable/landfront_ll.png +0 -0
- package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
- package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
- package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
- package/build/ExpoGaodeMapNaviView.d.ts +16 -0
- package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
- package/build/ExpoGaodeMapNaviView.js +74 -1
- package/build/ExpoGaodeMapNaviView.js.map +1 -1
- package/build/index.d.ts +56 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +452 -10
- package/build/index.js.map +1 -1
- package/build/map/ExpoGaodeMapModule.d.ts +15 -13
- package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapModule.js +31 -39
- package/build/map/ExpoGaodeMapModule.js.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.d.ts +5 -0
- package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
- package/build/map/ExpoGaodeMapView.d.ts +3 -4
- package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapView.js +28 -25
- package/build/map/ExpoGaodeMapView.js.map +1 -1
- package/build/map/components/overlays/Circle.d.ts.map +1 -1
- package/build/map/components/overlays/Circle.js +1 -30
- package/build/map/components/overlays/Circle.js.map +1 -1
- package/build/map/components/overlays/Cluster.d.ts.map +1 -1
- package/build/map/components/overlays/Cluster.js +1 -42
- package/build/map/components/overlays/Cluster.js.map +1 -1
- package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/map/components/overlays/HeatMap.js +21 -21
- package/build/map/components/overlays/HeatMap.js.map +1 -1
- package/build/map/components/overlays/Marker.d.ts.map +1 -1
- package/build/map/components/overlays/Marker.js +76 -80
- package/build/map/components/overlays/Marker.js.map +1 -1
- package/build/map/components/overlays/Polygon.d.ts.map +1 -1
- package/build/map/components/overlays/Polygon.js +1 -25
- package/build/map/components/overlays/Polygon.js.map +1 -1
- package/build/map/components/overlays/Polyline.d.ts.map +1 -1
- package/build/map/components/overlays/Polyline.js +1 -31
- package/build/map/components/overlays/Polyline.js.map +1 -1
- package/build/map/index.d.ts +9 -2
- package/build/map/index.d.ts.map +1 -1
- package/build/map/index.js +9 -2
- package/build/map/index.js.map +1 -1
- package/build/map/search/ExpoGaodeMapSearch.types.d.ts +340 -0
- package/build/map/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
- package/build/map/search/ExpoGaodeMapSearch.types.js +19 -0
- package/build/map/search/ExpoGaodeMapSearch.types.js.map +1 -0
- package/build/map/search/ExpoGaodeMapSearchModule.d.ts +74 -0
- package/build/map/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
- package/build/map/search/ExpoGaodeMapSearchModule.js +47 -0
- package/build/map/search/ExpoGaodeMapSearchModule.js.map +1 -0
- package/build/map/search/index.d.ts +156 -0
- package/build/map/search/index.d.ts.map +1 -0
- package/build/map/search/index.js +171 -0
- package/build/map/search/index.js.map +1 -0
- package/build/map/types/index.d.ts +2 -2
- package/build/map/types/index.d.ts.map +1 -1
- package/build/map/types/index.js.map +1 -1
- package/build/map/types/map-view.types.d.ts +4 -2
- package/build/map/types/map-view.types.d.ts.map +1 -1
- package/build/map/types/map-view.types.js.map +1 -1
- package/build/map/types/native-module.types.d.ts +11 -12
- package/build/map/types/native-module.types.d.ts.map +1 -1
- package/build/map/types/native-module.types.js.map +1 -1
- package/build/map/types/overlays.types.d.ts +9 -14
- package/build/map/types/overlays.types.d.ts.map +1 -1
- package/build/map/types/overlays.types.js.map +1 -1
- package/build/map/types/route-playback.types.d.ts +16 -0
- package/build/map/types/route-playback.types.d.ts.map +1 -1
- package/build/map/types/route-playback.types.js.map +1 -1
- package/build/map/utils/ErrorHandler.js +11 -11
- package/build/map/utils/ErrorHandler.js.map +1 -1
- package/build/map/utils/OfflineMapManager.d.ts +4 -0
- package/build/map/utils/OfflineMapManager.d.ts.map +1 -1
- package/build/map/utils/OfflineMapManager.js +6 -0
- package/build/map/utils/OfflineMapManager.js.map +1 -1
- package/build/types/coordinates.types.d.ts +3 -0
- package/build/types/coordinates.types.d.ts.map +1 -1
- package/build/types/coordinates.types.js.map +1 -1
- package/build/types/independent.types.d.ts +111 -12
- package/build/types/independent.types.d.ts.map +1 -1
- package/build/types/independent.types.js.map +1 -1
- package/build/types/native-module.types.d.ts +1 -1
- package/build/types/native-module.types.js.map +1 -1
- package/build/types/naviview.types.d.ts +304 -14
- package/build/types/naviview.types.d.ts.map +1 -1
- package/build/types/naviview.types.js.map +1 -1
- package/build/types/route.types.d.ts +12 -2
- package/build/types/route.types.d.ts.map +1 -1
- package/build/types/route.types.js.map +1 -1
- package/expo-module.config.json +4 -2
- package/ios/ExpoGaodeMapNaviView.swift +2331 -201
- package/ios/ExpoGaodeMapNaviViewModule.swift +109 -1
- package/ios/ExpoGaodeMapNavigation.podspec +2 -1
- package/ios/ExpoGaodeMapNavigationModule.swift +7 -5
- package/ios/managers/IndependentRouteManager.swift +90 -26
- package/ios/map/ExpoGaodeMapModule.swift +72 -21
- package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
- package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
- package/ios/map/ExpoGaodeMapView.swift +23 -5
- package/ios/map/GaodeMapPrivacyManager.swift +26 -18
- package/ios/map/cpp/GeometryEngine.cpp +112 -0
- package/ios/map/cpp/GeometryEngine.hpp +21 -0
- package/ios/map/modules/LocationManager.swift +37 -5
- package/ios/map/overlays/MarkerView.swift +11 -11
- package/ios/map/overlays/MarkerViewModule.swift +4 -4
- package/ios/map/overlays/PolylineView.swift +6 -12
- package/ios/map/utils/ClusterNative.h +8 -0
- package/ios/map/utils/ClusterNative.mm +27 -0
- package/ios/map/utils/PermissionManager.swift +115 -6
- package/ios/routes/drive/DriveTruckRouteCalculator.swift +165 -77
- package/ios/routes/walkride/WalkRideRouteCalculator.swift +127 -1
- package/ios/services/IndependentRouteService.swift +198 -39
- package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
- package/ios/services/NavigationLiveActivityManager.swift +359 -0
- package/package.json +22 -7
- package/plugin/build/withGaodeMap.d.ts +8 -0
- package/plugin/build/withGaodeMap.js +60 -4
- package/scripts/check-expo-modules.js +68 -0
- package/shared/cpp/GeometryEngine.cpp +112 -0
- package/shared/cpp/GeometryEngine.hpp +21 -0
- package/widget-template/README.md +46 -0
- package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/RouteCalculator.kt +0 -173
|
@@ -2,7 +2,17 @@ package expo.modules.gaodemap.navigation
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.graphics.Bitmap
|
|
6
|
+
import android.graphics.BitmapFactory
|
|
7
|
+
import android.graphics.Canvas
|
|
8
|
+
import android.graphics.Color
|
|
9
|
+
import android.graphics.Paint
|
|
10
|
+
import android.graphics.RectF
|
|
11
|
+
import android.graphics.Typeface
|
|
12
|
+
import android.net.Uri
|
|
5
13
|
import android.os.Bundle
|
|
14
|
+
import android.os.Handler
|
|
15
|
+
import android.os.Looper
|
|
6
16
|
import android.util.Log
|
|
7
17
|
import android.view.View
|
|
8
18
|
import android.view.ViewGroup
|
|
@@ -11,18 +21,60 @@ import com.amap.api.navi.AMapNaviListener
|
|
|
11
21
|
import com.amap.api.navi.AMapNaviView
|
|
12
22
|
import com.amap.api.navi.AMapNaviViewListener
|
|
13
23
|
import com.amap.api.navi.AMapNaviViewOptions
|
|
24
|
+
import com.amap.api.maps.model.BitmapDescriptorFactory
|
|
25
|
+
import com.amap.api.maps.model.LatLng
|
|
26
|
+
import com.amap.api.maps.model.Marker
|
|
27
|
+
import com.amap.api.maps.model.MarkerOptions
|
|
14
28
|
import com.amap.api.navi.enums.MapStyle
|
|
15
29
|
import com.amap.api.navi.enums.NaviType
|
|
16
30
|
import com.amap.api.navi.model.*
|
|
17
31
|
import expo.modules.gaodemap.map.modules.SDKInitializer
|
|
32
|
+
import expo.modules.gaodemap.navigation.managers.IndependentRouteManager
|
|
33
|
+
import expo.modules.gaodemap.navigation.services.NavigationForegroundService
|
|
34
|
+
import expo.modules.gaodemap.navigation.services.NavigationNotificationSnapshot
|
|
18
35
|
import expo.modules.kotlin.AppContext
|
|
19
36
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
20
37
|
import expo.modules.kotlin.views.ExpoView
|
|
21
|
-
import java.
|
|
38
|
+
import java.io.ByteArrayOutputStream
|
|
39
|
+
import java.io.File
|
|
40
|
+
import java.io.FileOutputStream
|
|
41
|
+
import java.security.MessageDigest
|
|
42
|
+
import java.util.Collections
|
|
43
|
+
import java.util.WeakHashMap
|
|
44
|
+
import java.net.URL
|
|
45
|
+
import kotlin.math.roundToInt
|
|
46
|
+
|
|
47
|
+
private data class NaviCustomWaypointMarkerModel(
|
|
48
|
+
val latitude: Double,
|
|
49
|
+
val longitude: Double,
|
|
50
|
+
val title: String,
|
|
51
|
+
val arrived: Boolean = false
|
|
52
|
+
)
|
|
22
53
|
|
|
23
54
|
@SuppressLint("ViewConstructor")
|
|
24
55
|
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
|
25
56
|
class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
57
|
+
companion object {
|
|
58
|
+
private val activeViews =
|
|
59
|
+
Collections.newSetFromMap(WeakHashMap<ExpoGaodeMapNaviView, Boolean>())
|
|
60
|
+
|
|
61
|
+
private fun snapshotActiveViews(): List<ExpoGaodeMapNaviView> =
|
|
62
|
+
synchronized(activeViews) { activeViews.toList() }
|
|
63
|
+
|
|
64
|
+
fun resumeActiveViews() {
|
|
65
|
+
snapshotActiveViews().forEach { it.onHostActivityForeground() }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fun pauseActiveViews() {
|
|
69
|
+
snapshotActiveViews().forEach { it.onHostActivityBackground() }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fun destroyActiveViews() {
|
|
73
|
+
snapshotActiveViews().forEach { it.onDestroy() }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private val independentRouteManager = IndependentRouteManager.shared
|
|
26
78
|
|
|
27
79
|
// 事件派发器
|
|
28
80
|
private val onNavigationReady by EventDispatcher()
|
|
@@ -38,6 +90,9 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
38
90
|
private val onGpsStatusChanged by EventDispatcher()
|
|
39
91
|
private val onNavigationInfoUpdate by EventDispatcher()
|
|
40
92
|
private val onGpsSignalWeak by EventDispatcher()
|
|
93
|
+
private val onNavigationVisualStateUpdate by EventDispatcher()
|
|
94
|
+
private val onLaneInfoUpdate by EventDispatcher()
|
|
95
|
+
private val onTrafficStatusesUpdate by EventDispatcher()
|
|
41
96
|
|
|
42
97
|
// Props - 使用 internal 避免自动生成 setter
|
|
43
98
|
internal var showCamera: Boolean = true
|
|
@@ -50,16 +105,41 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
50
105
|
internal var showUIElements: Boolean = true
|
|
51
106
|
internal var androidTrafficBarEnabled: Boolean = true
|
|
52
107
|
internal var isRouteListButtonShow: Boolean = true
|
|
53
|
-
internal var
|
|
108
|
+
internal var isTrafficButtonVisible: Boolean = true
|
|
54
109
|
internal var autoChangeZoom : Boolean = true
|
|
55
110
|
internal var autoLockCar: Boolean = true
|
|
56
|
-
internal var
|
|
111
|
+
internal var isTrafficLineEnabled: Boolean = true
|
|
57
112
|
internal var isRealCrossDisplayShow : Boolean = true
|
|
58
113
|
internal var isNaviArrowVisible : Boolean = true
|
|
59
|
-
internal var
|
|
114
|
+
internal var isLaneInfoVisible: Boolean = true
|
|
115
|
+
internal var isModeCrossDisplayVisible: Boolean = true
|
|
116
|
+
internal var isEyrieCrossDisplayVisible: Boolean = true
|
|
117
|
+
internal var isSecondActionVisible: Boolean = true
|
|
118
|
+
internal var isBackupOverlayVisible: Boolean = true
|
|
119
|
+
internal var isAfterRouteAutoGray: Boolean = true
|
|
60
120
|
internal var isVectorLineShow: Boolean = true
|
|
61
121
|
internal var isNaviTravelView : Boolean = false
|
|
62
122
|
internal var isCompassEnabled: Boolean = true
|
|
123
|
+
internal var isNaviStatusBarEnabled: Boolean = false
|
|
124
|
+
internal var lockZoomLevel: Int = 18
|
|
125
|
+
internal var lockTilt: Int = 35
|
|
126
|
+
internal var isEagleMapVisible: Boolean = false
|
|
127
|
+
internal var pointToCenterX: Double = 0.0
|
|
128
|
+
internal var pointToCenterY: Double = 0.0
|
|
129
|
+
internal var hideNativeTopInfoLayout: Boolean = false
|
|
130
|
+
internal var androidBackgroundNavigationNotificationEnabled: Boolean = false
|
|
131
|
+
internal var naviModeValue: Int = AMapNaviView.CAR_UP_MODE
|
|
132
|
+
internal var mapViewModeTypeValue: Int = 0
|
|
133
|
+
internal var carImageUri: String? = null
|
|
134
|
+
internal var carImageWidthDp: Double? = null
|
|
135
|
+
internal var carImageHeightDp: Double? = null
|
|
136
|
+
internal var fourCornersImageUri: String? = null
|
|
137
|
+
internal var startPointImageUri: String? = null
|
|
138
|
+
internal var wayPointImageUri: String? = null
|
|
139
|
+
internal var endPointImageUri: String? = null
|
|
140
|
+
private var customWaypointMarkers: List<NaviCustomWaypointMarkerModel> = emptyList()
|
|
141
|
+
private val renderedCustomWaypointMarkers = mutableListOf<Marker>()
|
|
142
|
+
private var activeIndependentRouteId: Int? = null
|
|
63
143
|
private val naviView: AMapNaviView by lazy(LazyThreadSafetyMode.NONE) {
|
|
64
144
|
AMapNaviView(context)
|
|
65
145
|
}
|
|
@@ -68,15 +148,803 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
68
148
|
private var endCoordinate: NaviLatLng? = null
|
|
69
149
|
|
|
70
150
|
private var lastAppliedTopPaddingPx: Int? = null
|
|
151
|
+
private var isDestroyed = false
|
|
152
|
+
private var isCrossVisible = false
|
|
153
|
+
private var isModeCrossVisible = false
|
|
154
|
+
private var isLaneInfoCurrentlyVisible = false
|
|
155
|
+
private var currentRouteTotalLength: Int? = null
|
|
156
|
+
private var sourceCarBitmap: Bitmap? = null
|
|
157
|
+
private var customCarBitmap: Bitmap? = null
|
|
158
|
+
private var customFourCornersBitmap: Bitmap? = null
|
|
159
|
+
private var customStartPointBitmap: Bitmap? = null
|
|
160
|
+
private var customWayPointBitmap: Bitmap? = null
|
|
161
|
+
private var customEndPointBitmap: Bitmap? = null
|
|
162
|
+
private var routeMarkerShowStartEndVia: Boolean = true
|
|
163
|
+
private var routeMarkerShowFootFerry: Boolean = true
|
|
164
|
+
private var routeMarkerShowForbidden: Boolean = true
|
|
165
|
+
private var routeMarkerShowRouteStartIcon: Boolean = true
|
|
166
|
+
private var routeMarkerShowRouteEndIcon: Boolean = true
|
|
167
|
+
private var cachedTurnIconImageUri: String? = null
|
|
168
|
+
private var cachedTurnIconContentHash: String? = null
|
|
169
|
+
private var hasLoggedMissingTurnIconBitmapApi = false
|
|
170
|
+
private var hasLoggedMissingNaviStatusBarApi = false
|
|
171
|
+
private var hasLoggedMissingNaviModeApi = false
|
|
172
|
+
private var isHostActivityInBackground: Boolean = false
|
|
173
|
+
private var isNavigationRunning: Boolean = false
|
|
174
|
+
private var latestNavigationNotificationSnapshot: NavigationNotificationSnapshot? = null
|
|
175
|
+
|
|
176
|
+
private fun registerActiveView() {
|
|
177
|
+
synchronized(activeViews) {
|
|
178
|
+
activeViews.add(this)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fun unregisterActiveView() {
|
|
183
|
+
synchronized(activeViews) {
|
|
184
|
+
activeViews.remove(this)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
71
187
|
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
|
|
188
|
+
private fun buildNavigationNotificationSnapshot(naviInfo: NaviInfo?): NavigationNotificationSnapshot {
|
|
189
|
+
if (naviInfo == null) {
|
|
190
|
+
return latestNavigationNotificationSnapshot ?: NavigationNotificationSnapshot(
|
|
191
|
+
pathRetainDistance = currentRouteTotalLength,
|
|
192
|
+
routeTotalDistance = currentRouteTotalLength,
|
|
193
|
+
turnIconImageUri = cachedTurnIconImageUri
|
|
194
|
+
)
|
|
195
|
+
}
|
|
75
196
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
197
|
+
return NavigationNotificationSnapshot(
|
|
198
|
+
currentRoadName = naviInfo.currentRoadName,
|
|
199
|
+
nextRoadName = naviInfo.nextRoadName,
|
|
200
|
+
pathRetainDistance = naviInfo.pathRetainDistance,
|
|
201
|
+
routeTotalDistance = currentRouteTotalLength,
|
|
202
|
+
pathRetainTime = naviInfo.pathRetainTime,
|
|
203
|
+
curStepRetainDistance = naviInfo.curStepRetainDistance,
|
|
204
|
+
iconType = naviInfo.iconType,
|
|
205
|
+
turnIconImageUri = cachedTurnIconImageUri
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private fun shouldRunBackgroundNavigationNotification(): Boolean {
|
|
210
|
+
return androidBackgroundNavigationNotificationEnabled &&
|
|
211
|
+
isNavigationRunning &&
|
|
212
|
+
isHostActivityInBackground
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private fun syncNavigationForegroundService(reason: String) {
|
|
216
|
+
val shouldRun = shouldRunBackgroundNavigationNotification()
|
|
217
|
+
Log.d(
|
|
218
|
+
"ExpoGaodeMapNaviView",
|
|
219
|
+
"syncNavigationForegroundService reason=$reason, shouldRun=$shouldRun, " +
|
|
220
|
+
"propEnabled=$androidBackgroundNavigationNotificationEnabled, " +
|
|
221
|
+
"isNavigationRunning=$isNavigationRunning, isHostActivityInBackground=$isHostActivityInBackground"
|
|
222
|
+
)
|
|
223
|
+
if (shouldRunBackgroundNavigationNotification()) {
|
|
224
|
+
val snapshot = latestNavigationNotificationSnapshot ?: buildNavigationNotificationSnapshot(null)
|
|
225
|
+
Log.d(
|
|
226
|
+
"ExpoGaodeMapNaviView",
|
|
227
|
+
"startOrUpdate notification snapshot: stepDistance=${snapshot.curStepRetainDistance}, " +
|
|
228
|
+
"remainDistance=${snapshot.pathRetainDistance}, routeTotal=${snapshot.routeTotalDistance}, " +
|
|
229
|
+
"remainTime=${snapshot.pathRetainTime}, " +
|
|
230
|
+
"nextRoad=${snapshot.nextRoadName}, turnIconUri=${snapshot.turnIconImageUri}"
|
|
231
|
+
)
|
|
232
|
+
NavigationForegroundService.startOrUpdate(context, snapshot)
|
|
233
|
+
Log.d("ExpoGaodeMapNaviView", "Navigation foreground notification enabled: $reason")
|
|
234
|
+
} else {
|
|
235
|
+
NavigationForegroundService.stop(context)
|
|
236
|
+
Log.d("ExpoGaodeMapNaviView", "Navigation foreground notification disabled: $reason")
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private fun updateNavigationNotification(naviInfo: NaviInfo) {
|
|
241
|
+
latestNavigationNotificationSnapshot = buildNavigationNotificationSnapshot(naviInfo)
|
|
242
|
+
Log.d(
|
|
243
|
+
"ExpoGaodeMapNaviView",
|
|
244
|
+
"updateNavigationNotification: stepDistance=${naviInfo.curStepRetainDistance}, " +
|
|
245
|
+
"remainDistance=${naviInfo.pathRetainDistance}, routeTotal=${latestNavigationNotificationSnapshot?.routeTotalDistance}, " +
|
|
246
|
+
"remainTime=${naviInfo.pathRetainTime}, " +
|
|
247
|
+
"currentRoad=${naviInfo.currentRoadName}, nextRoad=${naviInfo.nextRoadName}, " +
|
|
248
|
+
"turnIconUri=${latestNavigationNotificationSnapshot?.turnIconImageUri}"
|
|
249
|
+
)
|
|
250
|
+
if (shouldRunBackgroundNavigationNotification()) {
|
|
251
|
+
NavigationForegroundService.startOrUpdate(context, latestNavigationNotificationSnapshot)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fun onHostActivityForeground() {
|
|
256
|
+
Log.d("ExpoGaodeMapNaviView", "onHostActivityForeground")
|
|
257
|
+
isHostActivityInBackground = false
|
|
258
|
+
onResume()
|
|
259
|
+
syncNavigationForegroundService("host_foreground")
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fun onHostActivityBackground() {
|
|
263
|
+
Log.d("ExpoGaodeMapNaviView", "onHostActivityBackground")
|
|
264
|
+
isHostActivityInBackground = true
|
|
265
|
+
onPause()
|
|
266
|
+
syncNavigationForegroundService("host_background")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private fun applyNaviStatusBarEnabledCompat(
|
|
270
|
+
options: AMapNaviViewOptions,
|
|
271
|
+
enabled: Boolean
|
|
272
|
+
) {
|
|
273
|
+
try {
|
|
274
|
+
val method = options.javaClass.getMethod(
|
|
275
|
+
"setNaviStatusBarEnabled",
|
|
276
|
+
java.lang.Boolean.TYPE
|
|
277
|
+
)
|
|
278
|
+
method.invoke(options, enabled)
|
|
279
|
+
} catch (_: NoSuchMethodException) {
|
|
280
|
+
if (!hasLoggedMissingNaviStatusBarApi) {
|
|
281
|
+
hasLoggedMissingNaviStatusBarApi = true
|
|
282
|
+
Log.w(
|
|
283
|
+
"ExpoGaodeMapNaviView",
|
|
284
|
+
"AMapNaviViewOptions#setNaviStatusBarEnabled is unavailable in the current AMap SDK; skip applying naviStatusBarEnabled"
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
} catch (error: Throwable) {
|
|
288
|
+
Log.w(
|
|
289
|
+
"ExpoGaodeMapNaviView",
|
|
290
|
+
"Failed to apply naviStatusBarEnabled compatibly",
|
|
291
|
+
error
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private fun applyNaviModeCompat(
|
|
297
|
+
options: AMapNaviViewOptions,
|
|
298
|
+
mode: Int
|
|
299
|
+
) {
|
|
300
|
+
try {
|
|
301
|
+
val method = options.javaClass.getMethod(
|
|
302
|
+
"setNaviMode",
|
|
303
|
+
Integer.TYPE
|
|
304
|
+
)
|
|
305
|
+
method.invoke(options, mode)
|
|
306
|
+
} catch (_: NoSuchMethodException) {
|
|
307
|
+
if (!hasLoggedMissingNaviModeApi) {
|
|
308
|
+
hasLoggedMissingNaviModeApi = true
|
|
309
|
+
Log.w(
|
|
310
|
+
"ExpoGaodeMapNaviView",
|
|
311
|
+
"AMapNaviViewOptions#setNaviMode is unavailable in the current AMap SDK; skip applying naviMode on options"
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
} catch (error: Throwable) {
|
|
315
|
+
Log.w(
|
|
316
|
+
"ExpoGaodeMapNaviView",
|
|
317
|
+
"Failed to apply naviMode compatibly",
|
|
318
|
+
error
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private fun applyAutoNaviViewNightModeCompat(
|
|
324
|
+
options: AMapNaviViewOptions,
|
|
325
|
+
enabled: Boolean
|
|
326
|
+
) {
|
|
327
|
+
try {
|
|
328
|
+
val method = options.javaClass.getMethod(
|
|
329
|
+
"setAutoNaviViewNightMode",
|
|
330
|
+
java.lang.Boolean.TYPE
|
|
331
|
+
)
|
|
332
|
+
method.invoke(options, enabled)
|
|
333
|
+
} catch (_: NoSuchMethodException) {
|
|
334
|
+
Log.w(
|
|
335
|
+
"ExpoGaodeMapNaviView",
|
|
336
|
+
"AMapNaviViewOptions#setAutoNaviViewNightMode is unavailable in the current AMap SDK; skip applying auto night mode"
|
|
337
|
+
)
|
|
338
|
+
} catch (error: Throwable) {
|
|
339
|
+
Log.w(
|
|
340
|
+
"ExpoGaodeMapNaviView",
|
|
341
|
+
"Failed to apply auto navi night mode compatibly",
|
|
342
|
+
error
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private fun applyNaviNightCompat(
|
|
348
|
+
options: AMapNaviViewOptions,
|
|
349
|
+
enabled: Boolean
|
|
350
|
+
) {
|
|
351
|
+
try {
|
|
352
|
+
val method = options.javaClass.getMethod(
|
|
353
|
+
"setNaviNight",
|
|
354
|
+
java.lang.Boolean.TYPE
|
|
355
|
+
)
|
|
356
|
+
method.invoke(options, enabled)
|
|
357
|
+
return
|
|
358
|
+
} catch (_: NoSuchMethodException) {
|
|
359
|
+
Log.w(
|
|
360
|
+
"ExpoGaodeMapNaviView",
|
|
361
|
+
"AMapNaviViewOptions#setNaviNight is unavailable in the current AMap SDK; fallback to setMapStyle"
|
|
362
|
+
)
|
|
363
|
+
} catch (error: Throwable) {
|
|
364
|
+
Log.w(
|
|
365
|
+
"ExpoGaodeMapNaviView",
|
|
366
|
+
"Failed to apply navi night compatibly, fallback to setMapStyle",
|
|
367
|
+
error
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (enabled) {
|
|
372
|
+
options.setMapStyle(MapStyle.NIGHT, null)
|
|
373
|
+
} else {
|
|
374
|
+
options.setMapStyle(MapStyle.DAY, null)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private fun applyMapViewModeTypeCompat(
|
|
379
|
+
options: AMapNaviViewOptions,
|
|
380
|
+
mode: Int
|
|
381
|
+
) {
|
|
382
|
+
when (mode) {
|
|
383
|
+
0 -> {
|
|
384
|
+
applyAutoNaviViewNightModeCompat(options, false)
|
|
385
|
+
applyNaviNightCompat(options, false)
|
|
386
|
+
}
|
|
387
|
+
1 -> {
|
|
388
|
+
applyAutoNaviViewNightModeCompat(options, false)
|
|
389
|
+
applyNaviNightCompat(options, true)
|
|
390
|
+
}
|
|
391
|
+
2 -> {
|
|
392
|
+
applyAutoNaviViewNightModeCompat(options, true)
|
|
393
|
+
}
|
|
394
|
+
3 -> {
|
|
395
|
+
// Android custom map style requires style path API that is not exposed yet.
|
|
396
|
+
applyAutoNaviViewNightModeCompat(options, false)
|
|
397
|
+
applyNaviNightCompat(options, false)
|
|
398
|
+
Log.w(
|
|
399
|
+
"ExpoGaodeMapNaviView",
|
|
400
|
+
"mapViewModeType=3 (custom) requires custom map style path support on Android; fallback to day mode"
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
else -> {
|
|
404
|
+
applyAutoNaviViewNightModeCompat(options, false)
|
|
405
|
+
applyNaviNightCompat(options, false)
|
|
406
|
+
Log.w(
|
|
407
|
+
"ExpoGaodeMapNaviView",
|
|
408
|
+
"Unknown mapViewModeType=$mode; fallback to day mode"
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private fun createInitialViewOptions(): AMapNaviViewOptions {
|
|
415
|
+
return AMapNaviViewOptions().also { options ->
|
|
416
|
+
applyAllViewOptions(options)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private fun applyAllViewOptions(options: AMapNaviViewOptions) {
|
|
421
|
+
options.isLayoutVisible = showUIElements
|
|
422
|
+
options.isSettingMenuEnabled = true
|
|
423
|
+
options.isCompassEnabled = isCompassEnabled
|
|
424
|
+
options.isTrafficBarEnabled = androidTrafficBarEnabled
|
|
425
|
+
options.isRouteListButtonShow = isRouteListButtonShow
|
|
426
|
+
applyNaviStatusBarEnabledCompat(options, isNaviStatusBarEnabled)
|
|
427
|
+
|
|
428
|
+
options.isTrafficLayerEnabled = isTrafficButtonVisible
|
|
429
|
+
options.isTrafficLine = isTrafficLineEnabled
|
|
430
|
+
|
|
431
|
+
options.isRealCrossDisplayShow = isRealCrossDisplayShow
|
|
432
|
+
options.setModeCrossDisplayShow(isModeCrossDisplayVisible)
|
|
433
|
+
options.isLaneInfoShow = isLaneInfoVisible
|
|
434
|
+
options.isEyrieCrossDisplay = isEyrieCrossDisplayVisible
|
|
435
|
+
|
|
436
|
+
options.isCameraBubbleShow = showCamera
|
|
437
|
+
options.isShowCameraDistance = true
|
|
438
|
+
options.isWidgetOverSpeedPulseEffective = true
|
|
439
|
+
|
|
440
|
+
options.isAutoDrawRoute = true
|
|
441
|
+
options.isNaviArrowVisible = isNaviArrowVisible
|
|
442
|
+
options.isSecondActionVisible = isSecondActionVisible
|
|
443
|
+
options.isDrawBackUpOverlay = isBackupOverlayVisible
|
|
444
|
+
applyLeaderLineSetting(options, isVectorLineShow)
|
|
445
|
+
|
|
446
|
+
options.isAutoLockCar = autoLockCar
|
|
447
|
+
options.lockMapDelayed = 5000L
|
|
448
|
+
options.isAutoDisplayOverview = false
|
|
449
|
+
options.isAutoChangeZoom = autoChangeZoom
|
|
450
|
+
options.zoom = lockZoomLevel.coerceIn(14, 18)
|
|
451
|
+
options.tilt = lockTilt.coerceIn(0, 60)
|
|
452
|
+
applyNaviModeCompat(options, naviModeValue)
|
|
453
|
+
if (pointToCenterX > 0.0 && pointToCenterY > 0.0) {
|
|
454
|
+
options.setPointToCenter(pointToCenterX, pointToCenterY)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
options.isAfterRouteAutoGray = isAfterRouteAutoGray
|
|
458
|
+
options.isSensorEnable = true
|
|
459
|
+
applyMapViewModeTypeCompat(options, mapViewModeTypeValue)
|
|
460
|
+
options.isEagleMapVisible = isEagleMapVisible
|
|
461
|
+
applyCustomAnnotationBitmaps(options)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private fun applyBitmapOptionCompat(
|
|
465
|
+
options: AMapNaviViewOptions,
|
|
466
|
+
methodName: String,
|
|
467
|
+
bitmap: Bitmap?
|
|
468
|
+
) {
|
|
469
|
+
try {
|
|
470
|
+
val method = options.javaClass.getMethod(methodName, Bitmap::class.java)
|
|
471
|
+
method.invoke(options, bitmap)
|
|
472
|
+
} catch (_: NoSuchMethodException) {
|
|
473
|
+
Log.w(
|
|
474
|
+
"ExpoGaodeMapNaviView",
|
|
475
|
+
"AMapNaviViewOptions#$methodName is unavailable in the current AMap SDK; skip applying custom bitmap"
|
|
476
|
+
)
|
|
477
|
+
} catch (error: Throwable) {
|
|
478
|
+
Log.w(
|
|
479
|
+
"ExpoGaodeMapNaviView",
|
|
480
|
+
"Failed to apply $methodName compatibly",
|
|
481
|
+
error
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private fun applyCustomAnnotationBitmaps(options: AMapNaviViewOptions) {
|
|
487
|
+
applyBitmapOptionCompat(options, "setCarBitmap", customCarBitmap)
|
|
488
|
+
applyBitmapOptionCompat(options, "setFourCornersBitmap", customFourCornersBitmap)
|
|
489
|
+
applyBitmapOptionCompat(options, "setStartPointBitmap", customStartPointBitmap)
|
|
490
|
+
applyBitmapOptionCompat(options, "setWayPointBitmap", customWayPointBitmap)
|
|
491
|
+
applyBitmapOptionCompat(options, "setEndPointBitmap", customEndPointBitmap)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private fun refreshViewOptionsFromState(reason: String) {
|
|
495
|
+
if (isDestroyed) {
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
naviView.viewOptions = createInitialViewOptions()
|
|
499
|
+
refreshNaviUILayout(reason)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private fun loadBitmapFromSource(imagePath: String): Bitmap? {
|
|
503
|
+
return try {
|
|
504
|
+
when {
|
|
505
|
+
imagePath.startsWith("http://") || imagePath.startsWith("https://") -> {
|
|
506
|
+
URL(imagePath).openStream().use { BitmapFactory.decodeStream(it) }
|
|
507
|
+
}
|
|
508
|
+
imagePath.startsWith("file://") -> {
|
|
509
|
+
BitmapFactory.decodeFile(imagePath.substring(7))
|
|
510
|
+
}
|
|
511
|
+
else -> {
|
|
512
|
+
val fileName = imagePath.substringBeforeLast('.')
|
|
513
|
+
val resId = context.resources.getIdentifier(
|
|
514
|
+
fileName,
|
|
515
|
+
"drawable",
|
|
516
|
+
context.packageName
|
|
517
|
+
)
|
|
518
|
+
if (resId != 0) {
|
|
519
|
+
BitmapFactory.decodeResource(context.resources, resId)
|
|
520
|
+
} else {
|
|
521
|
+
BitmapFactory.decodeFile(imagePath)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
} catch (_: Throwable) {
|
|
526
|
+
null
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private fun resizeCarBitmapIfNeeded(source: Bitmap?): Bitmap? {
|
|
531
|
+
val rawBitmap = source ?: return null
|
|
532
|
+
val widthDp = carImageWidthDp?.takeIf { it > 0.0 }
|
|
533
|
+
val heightDp = carImageHeightDp?.takeIf { it > 0.0 }
|
|
534
|
+
if (widthDp == null || heightDp == null) {
|
|
535
|
+
return rawBitmap
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
val density = context.resources.displayMetrics.density
|
|
539
|
+
val widthPx = (widthDp * density).roundToInt().coerceAtLeast(1)
|
|
540
|
+
val heightPx = (heightDp * density).roundToInt().coerceAtLeast(1)
|
|
541
|
+
|
|
542
|
+
if (rawBitmap.width == widthPx && rawBitmap.height == heightPx) {
|
|
543
|
+
return rawBitmap
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return Bitmap.createScaledBitmap(rawBitmap, widthPx, heightPx, true)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private fun updateCustomAnnotationBitmap(
|
|
550
|
+
uri: String?,
|
|
551
|
+
getCurrentUri: () -> String?,
|
|
552
|
+
setCurrentUri: (String?) -> Unit,
|
|
553
|
+
setBitmap: (Bitmap?) -> Unit,
|
|
554
|
+
reason: String
|
|
555
|
+
) {
|
|
556
|
+
val normalizedUri = uri?.takeIf { it.isNotBlank() }
|
|
557
|
+
setCurrentUri(normalizedUri)
|
|
558
|
+
|
|
559
|
+
if (normalizedUri == null) {
|
|
560
|
+
setBitmap(null)
|
|
561
|
+
refreshViewOptionsFromState("clear-$reason")
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
Thread {
|
|
566
|
+
val bitmap = loadBitmapFromSource(normalizedUri)
|
|
567
|
+
Handler(Looper.getMainLooper()).post {
|
|
568
|
+
if (isDestroyed || getCurrentUri() != normalizedUri) {
|
|
569
|
+
return@post
|
|
570
|
+
}
|
|
571
|
+
setBitmap(bitmap)
|
|
572
|
+
refreshViewOptionsFromState("apply-$reason")
|
|
573
|
+
}
|
|
574
|
+
}.start()
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
private fun applyRouteMarkerVisibleFromState() {
|
|
578
|
+
naviView.setRouteMarkerVisible(
|
|
579
|
+
routeMarkerShowStartEndVia,
|
|
580
|
+
routeMarkerShowFootFerry,
|
|
581
|
+
routeMarkerShowForbidden,
|
|
582
|
+
routeMarkerShowRouteStartIcon,
|
|
583
|
+
routeMarkerShowRouteEndIcon
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private fun commitViewOptions(mutator: (AMapNaviViewOptions) -> Unit) {
|
|
588
|
+
val options = naviView.viewOptions ?: AMapNaviViewOptions()
|
|
589
|
+
mutator(options)
|
|
590
|
+
naviView.viewOptions = options
|
|
591
|
+
refreshNaviUILayout("commitViewOptions")
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private fun logCurrentNaviPathState(reason: String) {
|
|
595
|
+
val naviPath = aMapNavi?.naviPath
|
|
596
|
+
val waypointCount = naviPath?.wayPoint?.size ?: 0
|
|
597
|
+
val waypointIndexCount = naviPath?.wayPointIndex?.size ?: 0
|
|
598
|
+
Log.d(
|
|
599
|
+
"ExpoGaodeMapNaviView",
|
|
600
|
+
"pathState[$reason]: routeType=${naviPath?.routeType} length=${naviPath?.allLength} time=${naviPath?.allTime} labels=${naviPath?.labels} labelId=${naviPath?.labelId} waypointCount=$waypointCount waypointIndexCount=$waypointIndexCount start=${naviPath?.startPoint} end=${naviPath?.endPoint}"
|
|
601
|
+
)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private fun stabilizeIndependentRouteRendering(reason: String) {
|
|
605
|
+
val routeId = activeIndependentRouteId ?: return
|
|
606
|
+
val delays = longArrayOf(0L, 180L, 420L)
|
|
607
|
+
delays.forEach { delayMillis ->
|
|
608
|
+
naviView.postDelayed({
|
|
609
|
+
if (isDestroyed) {
|
|
610
|
+
return@postDelayed
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
val selected = aMapNavi?.selectRouteId(routeId)
|
|
614
|
+
Log.d(
|
|
615
|
+
"ExpoGaodeMapNaviView",
|
|
616
|
+
"stabilizeIndependentRouteRendering[$reason/$delayMillis]: routeId=$routeId selected=$selected"
|
|
617
|
+
)
|
|
618
|
+
applyRouteMarkerVisibleFromState()
|
|
619
|
+
logCurrentNaviPathState("stabilize-$reason-$delayMillis")
|
|
620
|
+
} catch (error: Exception) {
|
|
621
|
+
Log.e(
|
|
622
|
+
"ExpoGaodeMapNaviView",
|
|
623
|
+
"Failed to stabilize independent route rendering: reason=$reason delay=$delayMillis routeId=$routeId",
|
|
624
|
+
error
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
}, delayMillis)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private fun refreshNaviUILayout(reason: String) {
|
|
632
|
+
if (isDestroyed) {
|
|
633
|
+
return
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
naviView.post {
|
|
637
|
+
if (isDestroyed) {
|
|
638
|
+
return@post
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
updateTopInsetPadding()
|
|
642
|
+
updateNativeTopInfoLayoutVisibility()
|
|
643
|
+
|
|
644
|
+
val needsAggressiveRefresh =
|
|
645
|
+
hideNativeTopInfoLayout ||
|
|
646
|
+
!showUIElements ||
|
|
647
|
+
androidStatusBarPaddingTopDp != null
|
|
648
|
+
|
|
649
|
+
if (needsAggressiveRefresh) {
|
|
650
|
+
naviView.requestLayout()
|
|
651
|
+
naviView.forceLayout()
|
|
652
|
+
naviView.invalidate()
|
|
653
|
+
naviView.postInvalidateOnAnimation()
|
|
654
|
+
requestLayout()
|
|
655
|
+
invalidate()
|
|
656
|
+
postInvalidateOnAnimation()
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
Log.d("ExpoGaodeMapNaviView", "refreshNaviUILayout: $reason")
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private fun shouldHideNativeTopInfoView(view: View): Boolean {
|
|
664
|
+
val className = view.javaClass.name
|
|
665
|
+
return className.contains("BaseNaviInfoLayout") ||
|
|
666
|
+
className.contains("NaviInfoLayout_")
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private fun updateNativeTopInfoLayoutVisibility() {
|
|
670
|
+
if (isDestroyed) {
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (!hideNativeTopInfoLayout) {
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
val queue = ArrayDeque<View>()
|
|
679
|
+
val targetVisibility = View.GONE
|
|
680
|
+
queue.add(naviView)
|
|
681
|
+
|
|
682
|
+
while (queue.isNotEmpty()) {
|
|
683
|
+
val current = queue.removeFirst()
|
|
684
|
+
if (current is ViewGroup) {
|
|
685
|
+
for (index in 0 until current.childCount) {
|
|
686
|
+
queue.add(current.getChildAt(index))
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (!shouldHideNativeTopInfoView(current)) {
|
|
691
|
+
continue
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (current.visibility != targetVisibility) {
|
|
695
|
+
current.visibility = targetVisibility
|
|
696
|
+
current.requestLayout()
|
|
697
|
+
current.invalidate()
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private fun suppressNativeTopInfoLayoutFlash() {
|
|
703
|
+
if (!hideNativeTopInfoLayout || isDestroyed) {
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
val delays = longArrayOf(0L, 16L, 48L, 96L, 180L)
|
|
708
|
+
delays.forEach { delayMs ->
|
|
709
|
+
naviView.postDelayed({
|
|
710
|
+
if (!isDestroyed) {
|
|
711
|
+
updateNativeTopInfoLayoutVisibility()
|
|
712
|
+
}
|
|
713
|
+
}, delayMs)
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private fun emitVisualStateUpdate() {
|
|
718
|
+
onNavigationVisualStateUpdate(
|
|
719
|
+
mapOf(
|
|
720
|
+
"isCrossVisible" to isCrossVisible,
|
|
721
|
+
"isModeCrossVisible" to isModeCrossVisible,
|
|
722
|
+
"isLaneInfoVisible" to isLaneInfoCurrentlyVisible
|
|
723
|
+
)
|
|
724
|
+
)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private fun extractLaneValues(raw: Any?): List<Int> {
|
|
728
|
+
return when (raw) {
|
|
729
|
+
is IntArray -> raw.map { it.toInt() }
|
|
730
|
+
is ByteArray -> raw.map { it.toInt() and 0xFF }
|
|
731
|
+
is ShortArray -> raw.map { it.toInt() }
|
|
732
|
+
is Array<*> -> raw.mapNotNull { (it as? Number)?.toInt() }
|
|
733
|
+
else -> emptyList()
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private fun getLaneArrayValue(laneInfo: AMapLaneInfo, fieldName: String): List<Int> {
|
|
738
|
+
val reflectedValue = runCatching {
|
|
739
|
+
laneInfo.javaClass.getField(fieldName).get(laneInfo)
|
|
740
|
+
}.getOrElse {
|
|
741
|
+
runCatching {
|
|
742
|
+
val getterName = "get" + fieldName.replaceFirstChar { char ->
|
|
743
|
+
if (char.isLowerCase()) {
|
|
744
|
+
char.titlecase()
|
|
745
|
+
} else {
|
|
746
|
+
char.toString()
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
laneInfo.javaClass.getMethod(getterName).invoke(laneInfo)
|
|
750
|
+
}.getOrNull()
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return extractLaneValues(reflectedValue)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private fun resolveLaneCount(laneInfo: AMapLaneInfo): Int {
|
|
757
|
+
val reflectedCount = runCatching {
|
|
758
|
+
laneInfo.javaClass.getField("laneCount").get(laneInfo) as? Number
|
|
759
|
+
}.getOrElse {
|
|
760
|
+
runCatching {
|
|
761
|
+
laneInfo.javaClass.getMethod("getLaneCount").invoke(laneInfo) as? Number
|
|
762
|
+
}.getOrNull()
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return reflectedCount?.toInt()?.coerceAtLeast(0) ?: 0
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
private fun serializeLaneInfo(laneInfo: AMapLaneInfo?): Map<String, Any>? {
|
|
769
|
+
if (laneInfo == null) {
|
|
770
|
+
return null
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
val backgroundLane = getLaneArrayValue(laneInfo, "backgroundLane")
|
|
774
|
+
val frontLane = getLaneArrayValue(laneInfo, "frontLane")
|
|
775
|
+
val declaredCount = resolveLaneCount(laneInfo)
|
|
776
|
+
val sentinelIndex = backgroundLane.indexOf(255).takeIf { it >= 0 }
|
|
777
|
+
|
|
778
|
+
val resolvedCount = listOfNotNull(
|
|
779
|
+
sentinelIndex,
|
|
780
|
+
declaredCount.takeIf { it > 0 },
|
|
781
|
+
backgroundLane.size.takeIf { it > 0 },
|
|
782
|
+
frontLane.size.takeIf { it > 0 }
|
|
783
|
+
).minOrNull() ?: 0
|
|
784
|
+
|
|
785
|
+
if (resolvedCount <= 0) {
|
|
786
|
+
return null
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
val normalizedBackground = (0 until resolvedCount).map { index ->
|
|
790
|
+
backgroundLane.getOrNull(index) ?: 255
|
|
791
|
+
}
|
|
792
|
+
val normalizedFront = (0 until resolvedCount).map { index ->
|
|
793
|
+
frontLane.getOrNull(index) ?: 255
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return mapOf(
|
|
797
|
+
"laneCount" to resolvedCount,
|
|
798
|
+
"backgroundLane" to normalizedBackground,
|
|
799
|
+
"frontLane" to normalizedFront
|
|
800
|
+
)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private fun resolveNextTurnIconType(currentSegmentIndex: Int): Int? {
|
|
804
|
+
if (currentSegmentIndex < 0) {
|
|
805
|
+
return null
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
val steps = aMapNavi?.naviPath?.steps ?: return null
|
|
809
|
+
val nextSegmentIndex = currentSegmentIndex + 1
|
|
810
|
+
if (nextSegmentIndex !in steps.indices) {
|
|
811
|
+
return null
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return try {
|
|
815
|
+
steps[nextSegmentIndex].iconType
|
|
816
|
+
} catch (_: Throwable) {
|
|
817
|
+
null
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Android 导航 SDK 仅透出当前转向图 bitmap,且不同版本方法可见性不完全一致,
|
|
823
|
+
* 这里走反射兼容,避免因为 SDK 小版本差异导致编译直接中断。
|
|
824
|
+
*/
|
|
825
|
+
private fun extractTurnIconBitmap(naviInfo: NaviInfo): Bitmap? {
|
|
826
|
+
return try {
|
|
827
|
+
val method = naviInfo.javaClass.getMethod("getIconBitmap")
|
|
828
|
+
method.invoke(naviInfo) as? Bitmap
|
|
829
|
+
} catch (_: NoSuchMethodException) {
|
|
830
|
+
if (!hasLoggedMissingTurnIconBitmapApi) {
|
|
831
|
+
hasLoggedMissingTurnIconBitmapApi = true
|
|
832
|
+
Log.w(
|
|
833
|
+
"ExpoGaodeMapNaviView",
|
|
834
|
+
"NaviInfo#getIconBitmap is unavailable in the current AMap SDK; turnIconImage will be omitted on Android"
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
null
|
|
838
|
+
} catch (error: Throwable) {
|
|
839
|
+
Log.w("ExpoGaodeMapNaviView", "Failed to extract turn icon bitmap", error)
|
|
840
|
+
null
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private fun bitmapToPngBytes(bitmap: Bitmap): ByteArray? {
|
|
845
|
+
return try {
|
|
846
|
+
val outputStream = ByteArrayOutputStream()
|
|
847
|
+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
|
848
|
+
outputStream.toByteArray()
|
|
849
|
+
} catch (error: Throwable) {
|
|
850
|
+
Log.w("ExpoGaodeMapNaviView", "Failed to serialize turn icon bitmap", error)
|
|
851
|
+
null
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
private fun sha1Hex(bytes: ByteArray): String {
|
|
856
|
+
val digest = MessageDigest.getInstance("SHA-1").digest(bytes)
|
|
857
|
+
return digest.joinToString("") { "%02x".format(it) }
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private fun cacheTurnIconBitmap(bytes: ByteArray, prefix: String, contentHash: String): String? {
|
|
861
|
+
return try {
|
|
862
|
+
val directory = File(context.cacheDir, "expo_gaode_map_navigation_icons")
|
|
863
|
+
if (!directory.exists()) {
|
|
864
|
+
directory.mkdirs()
|
|
865
|
+
}
|
|
866
|
+
val file = File(directory, "${prefix}_${contentHash}.png")
|
|
867
|
+
if (!file.exists()) {
|
|
868
|
+
FileOutputStream(file).use { stream ->
|
|
869
|
+
stream.write(bytes)
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
Uri.fromFile(file).toString()
|
|
873
|
+
} catch (error: Throwable) {
|
|
874
|
+
Log.w("ExpoGaodeMapNaviView", "Failed to cache turn icon bitmap", error)
|
|
875
|
+
null
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
private fun deleteCachedIconFile(uriString: String?) {
|
|
880
|
+
if (uriString.isNullOrBlank()) {
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
runCatching {
|
|
884
|
+
val path = Uri.parse(uriString).path ?: return
|
|
885
|
+
File(path).delete()
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private fun updateCachedTurnIconImage(naviInfo: NaviInfo): String? {
|
|
890
|
+
val bitmap = extractTurnIconBitmap(naviInfo)
|
|
891
|
+
if (bitmap == null) {
|
|
892
|
+
deleteCachedIconFile(cachedTurnIconImageUri)
|
|
893
|
+
cachedTurnIconImageUri = null
|
|
894
|
+
cachedTurnIconContentHash = null
|
|
895
|
+
return null
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
val bytes = bitmapToPngBytes(bitmap) ?: return cachedTurnIconImageUri
|
|
899
|
+
val contentHash = sha1Hex(bytes)
|
|
900
|
+
if (contentHash == cachedTurnIconContentHash && !cachedTurnIconImageUri.isNullOrBlank()) {
|
|
901
|
+
return cachedTurnIconImageUri
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
val nextUri = cacheTurnIconBitmap(bytes, "turn_icon", contentHash) ?: return cachedTurnIconImageUri
|
|
905
|
+
val previousUri = cachedTurnIconImageUri
|
|
906
|
+
cachedTurnIconImageUri = nextUri
|
|
907
|
+
cachedTurnIconContentHash = contentHash
|
|
908
|
+
if (previousUri != nextUri) {
|
|
909
|
+
deleteCachedIconFile(previousUri)
|
|
910
|
+
}
|
|
911
|
+
return nextUri
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
private fun emitTrafficStatusesUpdate(retainDistance: Int? = null) {
|
|
915
|
+
val trafficStatuses = try {
|
|
916
|
+
aMapNavi?.getTrafficStatuses(0, 0)
|
|
917
|
+
} catch (_: Throwable) {
|
|
918
|
+
null
|
|
919
|
+
} ?: emptyList()
|
|
920
|
+
|
|
921
|
+
val totalLength = try {
|
|
922
|
+
aMapNavi?.naviPath?.allLength
|
|
923
|
+
} catch (_: Throwable) {
|
|
924
|
+
null
|
|
925
|
+
} ?: currentRouteTotalLength
|
|
926
|
+
|
|
927
|
+
currentRouteTotalLength = totalLength ?: currentRouteTotalLength
|
|
928
|
+
|
|
929
|
+
val payload = mutableMapOf<String, Any>(
|
|
930
|
+
"items" to trafficStatuses.map { status ->
|
|
931
|
+
mapOf(
|
|
932
|
+
"status" to status.status,
|
|
933
|
+
"length" to status.length
|
|
934
|
+
)
|
|
935
|
+
}
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
if ((totalLength ?: 0) > 0) {
|
|
939
|
+
payload["totalLength"] = totalLength as Int
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (retainDistance != null) {
|
|
943
|
+
payload["retainDistance"] = retainDistance
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
onTrafficStatusesUpdate(payload)
|
|
947
|
+
}
|
|
80
948
|
|
|
81
949
|
// 导航监听器
|
|
82
950
|
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
|
@@ -94,6 +962,10 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
94
962
|
|
|
95
963
|
override fun onStartNavi(type: Int) {
|
|
96
964
|
Log.d("ExpoGaodeMapNaviView", "导航开始: type=$type")
|
|
965
|
+
isNavigationRunning = true
|
|
966
|
+
syncNavigationForegroundService("on_start_navi")
|
|
967
|
+
logCurrentNaviPathState("onStartNavi")
|
|
968
|
+
stabilizeIndependentRouteRendering("onStartNavi")
|
|
97
969
|
onNavigationStarted(mapOf(
|
|
98
970
|
"type" to type,
|
|
99
971
|
"isEmulator" to (type == 1)
|
|
@@ -101,7 +973,7 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
101
973
|
}
|
|
102
974
|
|
|
103
975
|
override fun onTrafficStatusUpdate() {
|
|
104
|
-
|
|
976
|
+
emitTrafficStatusesUpdate()
|
|
105
977
|
}
|
|
106
978
|
|
|
107
979
|
override fun onLocationChange(location: AMapNaviLocation?) {
|
|
@@ -135,10 +1007,18 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
135
1007
|
}
|
|
136
1008
|
|
|
137
1009
|
override fun onEndEmulatorNavi() {
|
|
1010
|
+
isNavigationRunning = false
|
|
1011
|
+
syncNavigationForegroundService("on_end_emulator_navi")
|
|
1012
|
+
resetCustomWaypointMarkerArrivalState()
|
|
1013
|
+
activeIndependentRouteId = null
|
|
138
1014
|
onNavigationEnded(emptyMap())
|
|
139
1015
|
}
|
|
140
1016
|
|
|
141
1017
|
override fun onArriveDestination() {
|
|
1018
|
+
isNavigationRunning = false
|
|
1019
|
+
syncNavigationForegroundService("on_arrive_destination")
|
|
1020
|
+
resetCustomWaypointMarkerArrivalState()
|
|
1021
|
+
activeIndependentRouteId = null
|
|
142
1022
|
onArriveDestination(emptyMap())
|
|
143
1023
|
}
|
|
144
1024
|
|
|
@@ -156,6 +1036,14 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
156
1036
|
// GPS 导航
|
|
157
1037
|
aMapNavi?.startNavi(NaviType.GPS)
|
|
158
1038
|
}
|
|
1039
|
+
Handler(Looper.getMainLooper()).post {
|
|
1040
|
+
try {
|
|
1041
|
+
applyRouteMarkerVisibleFromState()
|
|
1042
|
+
refreshCustomWaypointMarkers("calculate-route-success")
|
|
1043
|
+
} catch (e: Exception) {
|
|
1044
|
+
Log.e("ExpoGaodeMapNaviView", "Failed to reapply route marker visibility after route success", e)
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
159
1047
|
}
|
|
160
1048
|
|
|
161
1049
|
override fun onCalculateRouteSuccess(result: AMapCalcRouteResult?) {
|
|
@@ -172,9 +1060,19 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
172
1060
|
// GPS 导航
|
|
173
1061
|
aMapNavi?.startNavi(NaviType.GPS)
|
|
174
1062
|
}
|
|
1063
|
+
Handler(Looper.getMainLooper()).post {
|
|
1064
|
+
try {
|
|
1065
|
+
applyRouteMarkerVisibleFromState()
|
|
1066
|
+
refreshCustomWaypointMarkers("calculate-route-success-result")
|
|
1067
|
+
} catch (e: Exception) {
|
|
1068
|
+
Log.e("ExpoGaodeMapNaviView", "Failed to reapply route marker visibility after route success", e)
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
175
1071
|
}
|
|
176
1072
|
|
|
177
1073
|
override fun onCalculateRouteFailure(errorCode: Int) {
|
|
1074
|
+
isNavigationRunning = false
|
|
1075
|
+
syncNavigationForegroundService("calculate_route_failure_code")
|
|
178
1076
|
onRouteCalculated(mapOf(
|
|
179
1077
|
"success" to false,
|
|
180
1078
|
"errorCode" to errorCode
|
|
@@ -182,6 +1080,8 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
182
1080
|
}
|
|
183
1081
|
|
|
184
1082
|
override fun onCalculateRouteFailure(result: AMapCalcRouteResult?) {
|
|
1083
|
+
isNavigationRunning = false
|
|
1084
|
+
syncNavigationForegroundService("calculate_route_failure_result")
|
|
185
1085
|
onRouteCalculated(mapOf(
|
|
186
1086
|
"success" to false,
|
|
187
1087
|
"errorInfo" to (result?.errorDescription ?: "Unknown error")
|
|
@@ -201,6 +1101,7 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
201
1101
|
}
|
|
202
1102
|
|
|
203
1103
|
override fun onArrivedWayPoint(wayPointIndex: Int) {
|
|
1104
|
+
markCustomWaypointArrived(wayPointIndex)
|
|
204
1105
|
onWayPointArrived(mapOf(
|
|
205
1106
|
"index" to wayPointIndex
|
|
206
1107
|
))
|
|
@@ -214,14 +1115,47 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
214
1115
|
|
|
215
1116
|
override fun onNaviInfoUpdate(naviInfo: NaviInfo?) {
|
|
216
1117
|
naviInfo?.let {
|
|
217
|
-
|
|
1118
|
+
val allLength = try {
|
|
1119
|
+
aMapNavi?.naviPath?.allLength
|
|
1120
|
+
} catch (_: Throwable) {
|
|
1121
|
+
null
|
|
1122
|
+
}
|
|
1123
|
+
val safeRetainDistance = it.pathRetainDistance.coerceAtLeast(0)
|
|
1124
|
+
currentRouteTotalLength = maxOf(
|
|
1125
|
+
currentRouteTotalLength ?: 0,
|
|
1126
|
+
allLength ?: 0,
|
|
1127
|
+
safeRetainDistance
|
|
1128
|
+
).takeIf { total -> total > 0 } ?: currentRouteTotalLength
|
|
1129
|
+
val nextIconType = resolveNextTurnIconType(it.curStep)
|
|
1130
|
+
val turnIconImage = updateCachedTurnIconImage(it)
|
|
1131
|
+
val payload = mutableMapOf<String, Any>(
|
|
1132
|
+
"naviMode" to it.naviType,
|
|
218
1133
|
"currentRoadName" to (it.currentRoadName ?: ""),
|
|
219
1134
|
"nextRoadName" to (it.nextRoadName ?: ""),
|
|
220
1135
|
"pathRetainDistance" to it.pathRetainDistance,
|
|
221
1136
|
"pathRetainTime" to it.pathRetainTime,
|
|
222
1137
|
"curStepRetainDistance" to it.curStepRetainDistance,
|
|
223
|
-
"curStepRetainTime" to it.curStepRetainTime
|
|
224
|
-
|
|
1138
|
+
"curStepRetainTime" to it.curStepRetainTime,
|
|
1139
|
+
"currentSpeed" to it.currentSpeed,
|
|
1140
|
+
"iconType" to it.iconType,
|
|
1141
|
+
"iconDirection" to it.iconType,
|
|
1142
|
+
"currentSegmentIndex" to it.curStep,
|
|
1143
|
+
"currentLinkIndex" to it.curLink,
|
|
1144
|
+
"currentPointIndex" to it.curPoint,
|
|
1145
|
+
"routeRemainTrafficLightCount" to it.routeRemainLightCount,
|
|
1146
|
+
"driveDistance" to 0,
|
|
1147
|
+
"driveTime" to 0
|
|
1148
|
+
)
|
|
1149
|
+
if (nextIconType != null) {
|
|
1150
|
+
payload["nextIconType"] = nextIconType
|
|
1151
|
+
}
|
|
1152
|
+
if (!turnIconImage.isNullOrBlank()) {
|
|
1153
|
+
payload["turnIconImage"] = turnIconImage
|
|
1154
|
+
}
|
|
1155
|
+
onNavigationInfoUpdate(payload)
|
|
1156
|
+
updateNavigationNotification(it)
|
|
1157
|
+
emitTrafficStatusesUpdate(it.pathRetainDistance)
|
|
1158
|
+
refreshNaviUILayout("onNaviInfoUpdate")
|
|
225
1159
|
}
|
|
226
1160
|
}
|
|
227
1161
|
|
|
@@ -239,30 +1173,57 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
239
1173
|
|
|
240
1174
|
override fun showCross(crossImg: AMapNaviCross?) {
|
|
241
1175
|
// 显示路口放大图
|
|
1176
|
+
isCrossVisible = true
|
|
1177
|
+
emitVisualStateUpdate()
|
|
1178
|
+
suppressNativeTopInfoLayoutFlash()
|
|
1179
|
+
refreshNaviUILayout("showCross")
|
|
242
1180
|
}
|
|
243
1181
|
|
|
244
1182
|
override fun hideCross() {
|
|
245
1183
|
// 隐藏路口放大图
|
|
1184
|
+
isCrossVisible = false
|
|
1185
|
+
emitVisualStateUpdate()
|
|
1186
|
+
suppressNativeTopInfoLayoutFlash()
|
|
1187
|
+
refreshNaviUILayout("hideCross")
|
|
246
1188
|
}
|
|
247
1189
|
|
|
248
1190
|
override fun showModeCross(modelCross: AMapModelCross?) {
|
|
249
1191
|
// 显示路口3D模型
|
|
1192
|
+
isModeCrossVisible = true
|
|
1193
|
+
emitVisualStateUpdate()
|
|
1194
|
+
suppressNativeTopInfoLayoutFlash()
|
|
1195
|
+
refreshNaviUILayout("showModeCross")
|
|
250
1196
|
}
|
|
251
1197
|
|
|
252
1198
|
override fun hideModeCross() {
|
|
253
1199
|
// 隐藏路口3D模型
|
|
1200
|
+
isModeCrossVisible = false
|
|
1201
|
+
emitVisualStateUpdate()
|
|
1202
|
+
suppressNativeTopInfoLayoutFlash()
|
|
1203
|
+
refreshNaviUILayout("hideModeCross")
|
|
254
1204
|
}
|
|
255
1205
|
|
|
256
1206
|
override fun showLaneInfo(laneInfo: AMapLaneInfo?) {
|
|
257
1207
|
// 显示车道信息
|
|
1208
|
+
isLaneInfoCurrentlyVisible = true
|
|
1209
|
+
emitVisualStateUpdate()
|
|
1210
|
+
serializeLaneInfo(laneInfo)?.let { onLaneInfoUpdate(it) }
|
|
1211
|
+
refreshNaviUILayout("showLaneInfo")
|
|
258
1212
|
}
|
|
259
1213
|
|
|
260
1214
|
override fun showLaneInfo(laneInfos: Array<out AMapLaneInfo>?, laneBackgroundInfo: ByteArray?, laneRecommendedInfo: ByteArray?) {
|
|
261
1215
|
// 显示车道信息(重载方法)
|
|
1216
|
+
isLaneInfoCurrentlyVisible = true
|
|
1217
|
+
emitVisualStateUpdate()
|
|
1218
|
+
serializeLaneInfo(laneInfos?.firstOrNull())?.let { onLaneInfoUpdate(it) }
|
|
1219
|
+
refreshNaviUILayout("showLaneInfoArray")
|
|
262
1220
|
}
|
|
263
1221
|
|
|
264
1222
|
override fun hideLaneInfo() {
|
|
265
1223
|
// 隐藏车道信息
|
|
1224
|
+
isLaneInfoCurrentlyVisible = false
|
|
1225
|
+
emitVisualStateUpdate()
|
|
1226
|
+
refreshNaviUILayout("hideLaneInfo")
|
|
266
1227
|
}
|
|
267
1228
|
|
|
268
1229
|
override fun notifyParallelRoad(parallelRoadType: Int) {
|
|
@@ -306,12 +1267,13 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
306
1267
|
SDKInitializer.restorePersistedState(appCtx)
|
|
307
1268
|
if (!SDKInitializer.isPrivacyReady()) {
|
|
308
1269
|
throw IllegalStateException(
|
|
309
|
-
"隐私协议未完成确认,请先调用 setPrivacyConfig
|
|
1270
|
+
"隐私协议未完成确认,请先调用 setPrivacyConfig"
|
|
310
1271
|
)
|
|
311
1272
|
}
|
|
312
1273
|
|
|
313
1274
|
// 初始化导航视图
|
|
314
1275
|
naviView.onCreate(Bundle())
|
|
1276
|
+
naviView.viewOptions = createInitialViewOptions()
|
|
315
1277
|
naviView.layoutParams = android.widget.FrameLayout.LayoutParams(
|
|
316
1278
|
android.widget.FrameLayout.LayoutParams.MATCH_PARENT,
|
|
317
1279
|
android.widget.FrameLayout.LayoutParams.MATCH_PARENT
|
|
@@ -323,8 +1285,6 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
323
1285
|
naviView.clipChildren = false
|
|
324
1286
|
naviView.clipToPadding = false
|
|
325
1287
|
|
|
326
|
-
ensureOverlayInsetHook()
|
|
327
|
-
|
|
328
1288
|
// 使用单例获取导航实例
|
|
329
1289
|
aMapNavi = AMapNavi.getInstance(appCtx)
|
|
330
1290
|
aMapNavi?.addAMapNaviListener(naviListener)
|
|
@@ -333,65 +1293,8 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
333
1293
|
aMapNavi?.setUseInnerVoice(true, true) // 启用内置语音,并回调文字
|
|
334
1294
|
//设置是否为骑步行视图
|
|
335
1295
|
aMapNavi?.isNaviTravelView = isNaviTravelView
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// === 基础界面控制 ===
|
|
340
|
-
// 注意:isLayoutVisible 控制整个导航UI布局的显示
|
|
341
|
-
// 设置为 true 将显示导航信息面板(包括距离、时间等)
|
|
342
|
-
options.isLayoutVisible = showUIElements
|
|
343
|
-
options.isSettingMenuEnabled = true // 显示设置菜单按钮
|
|
344
|
-
options.isCompassEnabled = isCompassEnabled // 显示指南针
|
|
345
|
-
options.isTrafficBarEnabled = androidTrafficBarEnabled // 显示路况条
|
|
346
|
-
options.isRouteListButtonShow = isRouteListButtonShow // 显示路线全览按钮
|
|
347
|
-
|
|
348
|
-
Log.d("ExpoGaodeMapNaviView", "导航UI配置: isLayoutVisible=true, 所有UI元素已启用")
|
|
349
|
-
|
|
350
|
-
// === 地图图层 ===
|
|
351
|
-
options.isTrafficLayerEnabled = isTrafficLayerEnabled // 显示交通路况图层
|
|
352
|
-
options.isTrafficLine = isTrafficLine // 显示交通路况线
|
|
353
|
-
|
|
354
|
-
// === 路口放大图和车道信息 ===
|
|
355
|
-
options.isRealCrossDisplayShow = isRealCrossDisplayShow // 显示实景路口放大图
|
|
356
|
-
options.setModeCrossDisplayShow(true) // 显示路口3D模型(使用方法而非属性)
|
|
357
|
-
options.isLaneInfoShow = true // 显示车道信息
|
|
358
|
-
options.isEyrieCrossDisplay = true // 显示鹰眼路口图
|
|
359
|
-
|
|
360
|
-
// === 摄像头和电子眼 ===
|
|
361
|
-
options.isCameraBubbleShow = showCamera // 显示摄像头气泡(已废弃但仍可用)
|
|
362
|
-
options.isShowCameraDistance = true // 显示与摄像头的距离
|
|
363
|
-
options.isWidgetOverSpeedPulseEffective = true // 超速脉冲效果
|
|
364
|
-
|
|
365
|
-
// === 路线和导航箭头 ===
|
|
366
|
-
options.isAutoDrawRoute = true // 自动绘制路线
|
|
367
|
-
options.isNaviArrowVisible = isNaviArrowVisible // 显示导航箭头
|
|
368
|
-
options.isSecondActionVisible = true // 显示辅助操作(如下个路口提示)
|
|
369
|
-
options.isDrawBackUpOverlay = true // 绘制备用路线覆盖物
|
|
370
|
-
if(isVectorLineShow)
|
|
371
|
-
options.isLeaderLineEnabled
|
|
372
|
-
|
|
373
|
-
// === 地图锁车和视角控制 ===
|
|
374
|
-
options.isAutoLockCar = autoLockCar // 自动锁车
|
|
375
|
-
options.lockMapDelayed = 5000L // 5秒后自动锁车(毫秒)
|
|
376
|
-
options.isAutoDisplayOverview = false // 不自动显示全览
|
|
377
|
-
options.isAutoChangeZoom = autoChangeZoom // 根据导航自动调整缩放级别
|
|
378
|
-
options.zoom = 18 // 锁车时的缩放级别 (14-18)
|
|
379
|
-
options.tilt = 35 // 锁车时的倾斜角度 (0-60)
|
|
380
|
-
|
|
381
|
-
// === 已走路线处理 ===
|
|
382
|
-
options.isAfterRouteAutoGray = isAfterRouteAutoGray // 走过的路线自动变灰
|
|
383
|
-
|
|
384
|
-
// === 传感器和定位 ===
|
|
385
|
-
options.isSensorEnable = true // 使用设备传感器
|
|
386
|
-
|
|
387
|
-
// === 夜间模式(已废弃但保留兼容) ===
|
|
388
|
-
// 建议使用 setMapStyle 方法设置地图样式
|
|
389
|
-
options.isAutoNaviViewNightMode = false // 不自动切换夜间模式
|
|
390
|
-
|
|
391
|
-
// === 鹰眼地图 ===
|
|
392
|
-
options.isEagleMapVisible = false // 不显示鹰眼地图(小地图)
|
|
393
|
-
|
|
394
|
-
naviView.viewOptions = options
|
|
1296
|
+
Log.d("ExpoGaodeMapNaviView", "导航UI配置初始化完成")
|
|
1297
|
+
registerActiveView()
|
|
395
1298
|
|
|
396
1299
|
naviView.post { updateTopInsetPadding() }
|
|
397
1300
|
|
|
@@ -413,7 +1316,10 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
413
1316
|
override fun onNextRoadClick() {}
|
|
414
1317
|
override fun onScanViewButtonClick() {}
|
|
415
1318
|
override fun onLockMap(isLock: Boolean) {}
|
|
416
|
-
override fun onNaviViewLoaded() {
|
|
1319
|
+
override fun onNaviViewLoaded() {
|
|
1320
|
+
updateNativeTopInfoLayoutVisibility()
|
|
1321
|
+
refreshNaviUILayout("onNaviViewLoaded")
|
|
1322
|
+
}
|
|
417
1323
|
override fun onMapTypeChanged(mapType: Int) {}
|
|
418
1324
|
})
|
|
419
1325
|
|
|
@@ -422,271 +1328,34 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
422
1328
|
}
|
|
423
1329
|
}
|
|
424
1330
|
|
|
425
|
-
private fun getStatusBarHeightPx(): Int {
|
|
426
|
-
return try {
|
|
427
|
-
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
|
|
428
|
-
if (resourceId > 0) context.resources.getDimensionPixelSize(resourceId) else 0
|
|
429
|
-
} catch (_: Exception) {
|
|
430
|
-
0
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
1331
|
private fun dpToPx(dp: Double): Int {
|
|
435
1332
|
val density = context.resources.displayMetrics.density
|
|
436
1333
|
return (dp * density + 0.5).toInt()
|
|
437
1334
|
}
|
|
438
1335
|
|
|
439
1336
|
private fun updateTopInsetPadding() {
|
|
440
|
-
val
|
|
441
|
-
val paddingTopPx = if (shouldApplyPadding) {
|
|
442
|
-
androidStatusBarPaddingTopDp?.let { dpToPx(it) } ?: getStatusBarHeightPx()
|
|
443
|
-
} else {
|
|
444
|
-
//默认返回状态栏高度
|
|
445
|
-
getStatusBarHeightPx()
|
|
446
|
-
}
|
|
1337
|
+
val paddingTopPx = androidStatusBarPaddingTopDp?.let { dpToPx(it) } ?: 0
|
|
447
1338
|
|
|
448
1339
|
if (lastAppliedTopPaddingPx == paddingTopPx) {
|
|
449
1340
|
return
|
|
450
1341
|
}
|
|
451
1342
|
|
|
452
1343
|
lastAppliedTopPaddingPx = paddingTopPx
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
applyTopInsetToOverlays(paddingTopPx)
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private fun ensureOverlayInsetHook() {
|
|
462
|
-
if (overlayHooked) {
|
|
463
|
-
return
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
overlayHooked = true
|
|
467
|
-
naviView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
|
468
|
-
applyTopInsetToOverlays(topInsetPx)
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
private fun applyTopInsetToOverlays(paddingTopPx: Int) {
|
|
473
|
-
if (paddingTopPx <= 0) {
|
|
474
|
-
if (overlayStates.isNotEmpty()) {
|
|
475
|
-
val iterator = overlayStates.entries.iterator()
|
|
476
|
-
while (iterator.hasNext()) {
|
|
477
|
-
val entry = iterator.next()
|
|
478
|
-
val view = entry.key
|
|
479
|
-
val state = entry.value
|
|
480
|
-
view.translationY = state.translationY
|
|
481
|
-
view.setPadding(view.paddingLeft, state.paddingTop, view.paddingRight, view.paddingBottom)
|
|
482
|
-
iterator.remove()
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
val rawTargets = findTopOverlayTargets(naviView)
|
|
489
|
-
val targets = filterTopLevelTargets(naviView, rawTargets)
|
|
490
|
-
if (targets.isEmpty()) {
|
|
491
|
-
val applied = applyTopPaddingToNaviUiLayer(paddingTopPx)
|
|
492
|
-
if (!applied) {
|
|
493
|
-
naviView.post { applyTopPaddingToNaviUiLayer(paddingTopPx) }
|
|
494
|
-
}
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
val targetSet = targets.toHashSet()
|
|
499
|
-
|
|
500
|
-
val iterator = overlayStates.entries.iterator()
|
|
501
|
-
while (iterator.hasNext()) {
|
|
502
|
-
val entry = iterator.next()
|
|
503
|
-
val view = entry.key
|
|
504
|
-
if (!targetSet.contains(view)) {
|
|
505
|
-
val state = entry.value
|
|
506
|
-
view.translationY = state.translationY
|
|
507
|
-
view.setPadding(view.paddingLeft, state.paddingTop, view.paddingRight, view.paddingBottom)
|
|
508
|
-
iterator.remove()
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
for (target in targets) {
|
|
513
|
-
if (!overlayStates.containsKey(target)) {
|
|
514
|
-
overlayStates[target] = OverlayState(
|
|
515
|
-
translationY = target.translationY,
|
|
516
|
-
paddingTop = target.paddingTop
|
|
517
|
-
)
|
|
518
|
-
}
|
|
519
|
-
ensureNoClipChain(target)
|
|
520
|
-
target.translationY = paddingTopPx.toFloat()
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
private fun ensureNoClipChain(view: View) {
|
|
525
|
-
var currentParent = view.parent
|
|
526
|
-
while (currentParent is ViewGroup) {
|
|
527
|
-
currentParent.clipChildren = false
|
|
528
|
-
currentParent.clipToPadding = false
|
|
529
|
-
if (currentParent === naviView) {
|
|
530
|
-
break
|
|
531
|
-
}
|
|
532
|
-
currentParent = currentParent.parent
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
private fun filterTopLevelTargets(root: ViewGroup, targets: List<View>): List<View> {
|
|
537
|
-
if (targets.size <= 1) {
|
|
538
|
-
return targets
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
val set = targets.toHashSet()
|
|
542
|
-
val result = ArrayList<View>(targets.size)
|
|
543
|
-
for (view in targets) {
|
|
544
|
-
var parent = view.parent
|
|
545
|
-
var hasAncestorInTargets = false
|
|
546
|
-
while (parent is View) {
|
|
547
|
-
if (parent === root) {
|
|
548
|
-
break
|
|
549
|
-
}
|
|
550
|
-
if (set.contains(parent)) {
|
|
551
|
-
hasAncestorInTargets = true
|
|
552
|
-
break
|
|
553
|
-
}
|
|
554
|
-
parent = parent.parent
|
|
555
|
-
}
|
|
556
|
-
if (!hasAncestorInTargets) {
|
|
557
|
-
result.add(view)
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
return result
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
private fun findTopOverlayTargets(root: ViewGroup): List<View> {
|
|
565
|
-
val result = ArrayList<View>()
|
|
566
|
-
val parentHeight = root.height
|
|
567
|
-
val parentWidth = root.width
|
|
568
|
-
if (parentHeight <= 0 || parentWidth <= 0) {
|
|
569
|
-
return result
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
val queue = ArrayDeque<View>()
|
|
573
|
-
for (i in 0 until root.childCount) {
|
|
574
|
-
queue.add(root.getChildAt(i))
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
while (queue.isNotEmpty()) {
|
|
578
|
-
val view = queue.removeFirst()
|
|
579
|
-
val group = view as? ViewGroup
|
|
580
|
-
if (group != null) {
|
|
581
|
-
for (i in 0 until group.childCount) {
|
|
582
|
-
queue.add(group.getChildAt(i))
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (!view.isShown) {
|
|
587
|
-
continue
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
val name = view.javaClass.name
|
|
591
|
-
if (name.contains("MapView", ignoreCase = true) ||
|
|
592
|
-
name.contains("Texture", ignoreCase = true) ||
|
|
593
|
-
name.contains("Surface", ignoreCase = true) ||
|
|
594
|
-
name.contains("GLSurface", ignoreCase = true)
|
|
595
|
-
) {
|
|
596
|
-
continue
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (view.height <= 0 || view.width <= 0) {
|
|
600
|
-
continue
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if (view.top > 1) {
|
|
604
|
-
continue
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if (view.height >= (parentHeight * 0.6f).toInt()) {
|
|
608
|
-
continue
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
val wideEnough = view.width >= (parentWidth * 0.5f).toInt()
|
|
612
|
-
val likelyUi = wideEnough && (view.isClickable || (view as? ViewGroup)?.childCount ?: 0 > 0)
|
|
613
|
-
if (!likelyUi) {
|
|
614
|
-
continue
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
result.add(view)
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
return result
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
private fun applyTopPaddingToNaviUiLayer(paddingTopPx: Int): Boolean {
|
|
624
|
-
val uiRoot = findNaviUiRoot(naviView) ?: return false
|
|
625
|
-
|
|
626
|
-
uiRoot.setPadding(uiRoot.paddingLeft, paddingTopPx, uiRoot.paddingRight, uiRoot.paddingBottom)
|
|
627
|
-
uiRoot.clipToPadding = false
|
|
628
|
-
return true
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
private fun findNaviUiRoot(root: ViewGroup): ViewGroup? {
|
|
632
|
-
var best: ViewGroup? = null
|
|
633
|
-
var bestScore = Int.MIN_VALUE
|
|
634
|
-
|
|
635
|
-
val queue = ArrayDeque<View>()
|
|
636
|
-
for (i in 0 until root.childCount) {
|
|
637
|
-
queue.add(root.getChildAt(i))
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
while (queue.isNotEmpty()) {
|
|
641
|
-
val view = queue.removeFirst()
|
|
642
|
-
val group = view as? ViewGroup
|
|
643
|
-
if (group != null) {
|
|
644
|
-
val score = scoreAsUiRootCandidate(group)
|
|
645
|
-
if (score > bestScore) {
|
|
646
|
-
bestScore = score
|
|
647
|
-
best = group
|
|
648
|
-
}
|
|
649
|
-
for (i in 0 until group.childCount) {
|
|
650
|
-
queue.add(group.getChildAt(i))
|
|
651
|
-
}
|
|
652
|
-
}
|
|
1344
|
+
if (paddingTopPx > 0) {
|
|
1345
|
+
naviView.setPadding(0, paddingTopPx, 0, 0)
|
|
1346
|
+
} else {
|
|
1347
|
+
naviView.setPadding(0, 0, 0, 0)
|
|
653
1348
|
}
|
|
654
|
-
|
|
655
|
-
return if (bestScore > 0) best else null
|
|
1349
|
+
naviView.requestLayout()
|
|
656
1350
|
}
|
|
657
1351
|
|
|
658
|
-
private fun
|
|
659
|
-
val
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
name.contains("GLSurface", ignoreCase = true)
|
|
664
|
-
) {
|
|
665
|
-
return Int.MIN_VALUE
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
var score = 0
|
|
669
|
-
|
|
670
|
-
val lp = group.layoutParams
|
|
671
|
-
if (lp != null) {
|
|
672
|
-
if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT && lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
|
|
673
|
-
score += 4
|
|
674
|
-
} else if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
|
|
675
|
-
score += 2
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (group.childCount >= 3) {
|
|
680
|
-
score += 2
|
|
681
|
-
} else if (group.childCount >= 1) {
|
|
682
|
-
score += 1
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (name.contains("RelativeLayout", ignoreCase = true) || name.contains("FrameLayout", ignoreCase = true)) {
|
|
686
|
-
score += 1
|
|
1352
|
+
private fun applyLeaderLineSetting(options: AMapNaviViewOptions, enabled: Boolean) {
|
|
1353
|
+
val color = if (enabled) {
|
|
1354
|
+
Color.argb(160, 48, 122, 246)
|
|
1355
|
+
} else {
|
|
1356
|
+
-1
|
|
687
1357
|
}
|
|
688
|
-
|
|
689
|
-
return score
|
|
1358
|
+
options.setLeaderLineEnabled(color)
|
|
690
1359
|
}
|
|
691
1360
|
|
|
692
1361
|
|
|
@@ -698,50 +1367,50 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
698
1367
|
|
|
699
1368
|
fun applyShowUIElements(visible: Boolean) {
|
|
700
1369
|
showUIElements = visible
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1370
|
+
commitViewOptions { options ->
|
|
1371
|
+
options.isLayoutVisible = visible
|
|
1372
|
+
}
|
|
704
1373
|
}
|
|
705
1374
|
|
|
706
1375
|
fun applyAndroidTrafficBarEnabled(enabled: Boolean) {
|
|
707
1376
|
androidTrafficBarEnabled = enabled
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1377
|
+
commitViewOptions { options ->
|
|
1378
|
+
options.isTrafficBarEnabled = enabled
|
|
1379
|
+
}
|
|
711
1380
|
}
|
|
712
1381
|
|
|
713
1382
|
fun applyShowTrafficButton(enabled: Boolean) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1383
|
+
isTrafficButtonVisible = enabled
|
|
1384
|
+
commitViewOptions { options ->
|
|
1385
|
+
options.isTrafficLayerEnabled = enabled
|
|
1386
|
+
}
|
|
718
1387
|
}
|
|
719
1388
|
|
|
720
1389
|
fun applyShowBrowseRouteButton(enabled: Boolean) {
|
|
721
1390
|
isRouteListButtonShow = enabled
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1391
|
+
commitViewOptions { options ->
|
|
1392
|
+
options.isRouteListButtonShow = enabled
|
|
1393
|
+
}
|
|
725
1394
|
}
|
|
726
1395
|
|
|
727
1396
|
fun applyShowGreyAfterPass(enabled: Boolean){
|
|
728
1397
|
isAfterRouteAutoGray = enabled
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1398
|
+
commitViewOptions { options ->
|
|
1399
|
+
options.isAfterRouteAutoGray = enabled
|
|
1400
|
+
}
|
|
732
1401
|
}
|
|
733
1402
|
|
|
734
1403
|
fun applyShowVectorline(enabled: Boolean){
|
|
735
1404
|
isVectorLineShow = enabled
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
naviView.viewOptions = options
|
|
1405
|
+
commitViewOptions { options ->
|
|
1406
|
+
applyLeaderLineSetting(options, enabled)
|
|
1407
|
+
}
|
|
740
1408
|
}
|
|
741
1409
|
|
|
742
1410
|
fun startNavigation(startLat: Double, startLng: Double, endLat: Double, endLng: Double, promise: expo.modules.kotlin.Promise) {
|
|
743
1411
|
Log.d("ExpoGaodeMapNaviView", "startNavigation: $startLat, $startLng, $endLat, $endLng, naviType: $naviType")
|
|
744
1412
|
try {
|
|
1413
|
+
resetCustomWaypointMarkerArrivalState()
|
|
745
1414
|
startCoordinate = NaviLatLng(startLat, startLng)
|
|
746
1415
|
endCoordinate = NaviLatLng(endLat, endLng)
|
|
747
1416
|
|
|
@@ -778,9 +1447,47 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
778
1447
|
}
|
|
779
1448
|
}
|
|
780
1449
|
|
|
1450
|
+
fun startNavigationWithIndependentPath(
|
|
1451
|
+
token: Int,
|
|
1452
|
+
routeId: Int?,
|
|
1453
|
+
routeIndex: Int?,
|
|
1454
|
+
requestedNaviType: Int?,
|
|
1455
|
+
promise: expo.modules.kotlin.Promise
|
|
1456
|
+
) {
|
|
1457
|
+
try {
|
|
1458
|
+
resetCustomWaypointMarkerArrivalState()
|
|
1459
|
+
val finalNaviType = requestedNaviType ?: naviType
|
|
1460
|
+
val result = independentRouteManager.start(context, token, finalNaviType, routeId, routeIndex)
|
|
1461
|
+
if (result.success) {
|
|
1462
|
+
activeIndependentRouteId = result.resolvedRouteId
|
|
1463
|
+
promise.resolve(
|
|
1464
|
+
mapOf(
|
|
1465
|
+
"success" to true,
|
|
1466
|
+
"message" to result.message,
|
|
1467
|
+
"token" to token,
|
|
1468
|
+
"naviType" to finalNaviType,
|
|
1469
|
+
"sdkNaviType" to result.sdkNaviType,
|
|
1470
|
+
"routeId" to result.resolvedRouteId,
|
|
1471
|
+
"pathCount" to result.pathCount,
|
|
1472
|
+
"mainPathIndex" to result.mainPathIndex
|
|
1473
|
+
)
|
|
1474
|
+
)
|
|
1475
|
+
} else {
|
|
1476
|
+
activeIndependentRouteId = null
|
|
1477
|
+
promise.reject("START_INDEPENDENT_NAVI_FAILED", result.message, null)
|
|
1478
|
+
}
|
|
1479
|
+
} catch (e: Exception) {
|
|
1480
|
+
activeIndependentRouteId = null
|
|
1481
|
+
promise.reject("START_INDEPENDENT_NAVI_ERROR", e.message, e)
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
781
1485
|
fun stopNavigation(promise: expo.modules.kotlin.Promise) {
|
|
782
1486
|
try {
|
|
783
1487
|
aMapNavi?.stopNavi()
|
|
1488
|
+
isNavigationRunning = false
|
|
1489
|
+
syncNavigationForegroundService("stop_navigation")
|
|
1490
|
+
resetCustomWaypointMarkerArrivalState()
|
|
784
1491
|
promise.resolve(mapOf(
|
|
785
1492
|
"success" to true,
|
|
786
1493
|
"message" to "导航已停止"
|
|
@@ -793,10 +1500,15 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
793
1500
|
// Prop setters - 使用不同的方法名避免与 var 属性的自动 setter 冲突
|
|
794
1501
|
fun applyShowCamera(show: Boolean) {
|
|
795
1502
|
showCamera = show
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1503
|
+
commitViewOptions { options ->
|
|
1504
|
+
options.isCameraBubbleShow = show
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
fun applyAndroidBackgroundNavigationNotificationEnabled(enabled: Boolean) {
|
|
1509
|
+
androidBackgroundNavigationNotificationEnabled = enabled
|
|
1510
|
+
Log.d("ExpoGaodeMapNaviView", "applyAndroidBackgroundNavigationNotificationEnabled=$enabled")
|
|
1511
|
+
syncNavigationForegroundService("prop_update")
|
|
800
1512
|
}
|
|
801
1513
|
|
|
802
1514
|
fun applyNaviType(type: Int) {
|
|
@@ -830,42 +1542,125 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
830
1542
|
|
|
831
1543
|
fun applyAutoLockCar(enabled: Boolean) {
|
|
832
1544
|
autoLockCar = enabled
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1545
|
+
commitViewOptions { options ->
|
|
1546
|
+
options.isAutoLockCar = enabled
|
|
1547
|
+
}
|
|
836
1548
|
}
|
|
837
1549
|
|
|
838
1550
|
fun applyAutoChangeZoom(enabled: Boolean) {
|
|
839
1551
|
autoChangeZoom = enabled
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1552
|
+
commitViewOptions { options ->
|
|
1553
|
+
options.isAutoChangeZoom = enabled
|
|
1554
|
+
}
|
|
843
1555
|
}
|
|
844
1556
|
|
|
845
1557
|
fun applyTrafficLayerEnabled(enabled: Boolean) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1558
|
+
isTrafficLineEnabled = enabled
|
|
1559
|
+
commitViewOptions { options ->
|
|
1560
|
+
options.isTrafficLine = enabled
|
|
1561
|
+
}
|
|
850
1562
|
}
|
|
851
1563
|
|
|
852
1564
|
fun applyRealCrossDisplay(enabled: Boolean) {
|
|
853
1565
|
isRealCrossDisplayShow = enabled
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1566
|
+
commitViewOptions { options ->
|
|
1567
|
+
options.isRealCrossDisplayShow = enabled
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
fun applyLaneInfoVisible(enabled: Boolean) {
|
|
1572
|
+
isLaneInfoVisible = enabled
|
|
1573
|
+
commitViewOptions { options ->
|
|
1574
|
+
options.isLaneInfoShow = enabled
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
fun applyModeCrossDisplay(enabled: Boolean) {
|
|
1579
|
+
isModeCrossDisplayVisible = enabled
|
|
1580
|
+
commitViewOptions { options ->
|
|
1581
|
+
options.setModeCrossDisplayShow(enabled)
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
fun applyEyrieCrossDisplay(enabled: Boolean) {
|
|
1586
|
+
isEyrieCrossDisplayVisible = enabled
|
|
1587
|
+
commitViewOptions { options ->
|
|
1588
|
+
options.isEyrieCrossDisplay = enabled
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
fun applySecondActionVisible(enabled: Boolean) {
|
|
1593
|
+
isSecondActionVisible = enabled
|
|
1594
|
+
commitViewOptions { options ->
|
|
1595
|
+
options.isSecondActionVisible = enabled
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
fun applyBackupOverlayVisible(enabled: Boolean) {
|
|
1600
|
+
isBackupOverlayVisible = enabled
|
|
1601
|
+
commitViewOptions { options ->
|
|
1602
|
+
options.isDrawBackUpOverlay = enabled
|
|
1603
|
+
}
|
|
857
1604
|
}
|
|
858
1605
|
|
|
859
1606
|
fun applyShowCompassEnabled(enabled: Boolean){
|
|
860
1607
|
isCompassEnabled = enabled
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1608
|
+
commitViewOptions { options ->
|
|
1609
|
+
options.isCompassEnabled = enabled
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
fun applyNaviStatusBarEnabled(enabled: Boolean) {
|
|
1614
|
+
isNaviStatusBarEnabled = enabled
|
|
1615
|
+
commitViewOptions { options ->
|
|
1616
|
+
applyNaviStatusBarEnabledCompat(options, enabled)
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
fun applyLockZoom(level: Int) {
|
|
1621
|
+
lockZoomLevel = level.coerceIn(14, 18)
|
|
1622
|
+
commitViewOptions { options ->
|
|
1623
|
+
options.zoom = lockZoomLevel
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
fun applyLockTilt(tilt: Int) {
|
|
1628
|
+
lockTilt = tilt.coerceIn(0, 60)
|
|
1629
|
+
commitViewOptions { options ->
|
|
1630
|
+
options.tilt = lockTilt
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
fun applyEagleMapVisible(enabled: Boolean) {
|
|
1635
|
+
isEagleMapVisible = enabled
|
|
1636
|
+
commitViewOptions { options ->
|
|
1637
|
+
options.isEagleMapVisible = enabled
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
fun applyPointToCenter(x: Double, y: Double) {
|
|
1642
|
+
pointToCenterX = x
|
|
1643
|
+
pointToCenterY = y
|
|
1644
|
+
commitViewOptions { options ->
|
|
1645
|
+
if (x > 0.0 && y > 0.0) {
|
|
1646
|
+
options.setPointToCenter(x, y)
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
fun applyHideNativeTopInfoLayout(hidden: Boolean) {
|
|
1652
|
+
hideNativeTopInfoLayout = hidden
|
|
1653
|
+
updateNativeTopInfoLayoutVisibility()
|
|
1654
|
+
refreshNaviUILayout("applyHideNativeTopInfoLayout")
|
|
864
1655
|
}
|
|
865
1656
|
|
|
866
1657
|
|
|
867
1658
|
fun applyNaviMode(mode: Int) {
|
|
868
|
-
|
|
1659
|
+
naviModeValue = mode
|
|
1660
|
+
commitViewOptions { options ->
|
|
1661
|
+
applyNaviModeCompat(options, mode)
|
|
1662
|
+
}
|
|
1663
|
+
// 兼容旧版接口,保持与 options 一致
|
|
869
1664
|
naviView.naviMode = mode
|
|
870
1665
|
}
|
|
871
1666
|
|
|
@@ -875,21 +1670,26 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
875
1670
|
}
|
|
876
1671
|
|
|
877
1672
|
fun applyNightMode(enabled: Boolean) {
|
|
878
|
-
|
|
1673
|
+
mapViewModeTypeValue = if (enabled) 1 else 0
|
|
879
1674
|
try {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
options.setMapStyle(MapStyle.NIGHT, null)
|
|
883
|
-
}else{
|
|
884
|
-
options.setMapStyle(MapStyle.DAY, null)
|
|
1675
|
+
commitViewOptions { options ->
|
|
1676
|
+
applyMapViewModeTypeCompat(options, mapViewModeTypeValue)
|
|
885
1677
|
}
|
|
886
|
-
// options.isNightMode = enabled // 该属性可能不存在
|
|
887
|
-
// 可以通过其他方式设置夜间模式
|
|
888
|
-
naviView.viewOptions = options
|
|
889
1678
|
} catch (e: Exception) {
|
|
890
1679
|
Log.e("ExpoGaodeMapNaviView", "Failed to set night mode", e)
|
|
891
1680
|
}
|
|
892
1681
|
}
|
|
1682
|
+
|
|
1683
|
+
fun applyMapViewModeType(mode: Int) {
|
|
1684
|
+
mapViewModeTypeValue = mode
|
|
1685
|
+
try {
|
|
1686
|
+
commitViewOptions { options ->
|
|
1687
|
+
applyMapViewModeTypeCompat(options, mode)
|
|
1688
|
+
}
|
|
1689
|
+
} catch (e: Exception) {
|
|
1690
|
+
Log.e("ExpoGaodeMapNaviView", "Failed to apply mapViewModeType=$mode", e)
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
893
1693
|
|
|
894
1694
|
/**
|
|
895
1695
|
* 设置是否显示自车和罗盘
|
|
@@ -905,6 +1705,200 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
905
1705
|
}
|
|
906
1706
|
}
|
|
907
1707
|
|
|
1708
|
+
fun applyCarImage(uri: String?) {
|
|
1709
|
+
updateCustomAnnotationBitmap(
|
|
1710
|
+
uri = uri,
|
|
1711
|
+
getCurrentUri = { carImageUri },
|
|
1712
|
+
setCurrentUri = { carImageUri = it },
|
|
1713
|
+
setBitmap = {
|
|
1714
|
+
sourceCarBitmap = it
|
|
1715
|
+
customCarBitmap = resizeCarBitmapIfNeeded(it)
|
|
1716
|
+
},
|
|
1717
|
+
reason = "carBitmap"
|
|
1718
|
+
)
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
fun applyCarImageSize(widthDp: Double?, heightDp: Double?) {
|
|
1722
|
+
carImageWidthDp = widthDp
|
|
1723
|
+
carImageHeightDp = heightDp
|
|
1724
|
+
customCarBitmap = resizeCarBitmapIfNeeded(sourceCarBitmap)
|
|
1725
|
+
refreshViewOptionsFromState("apply-carBitmap-size")
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
fun applyFourCornersImage(uri: String?) {
|
|
1729
|
+
updateCustomAnnotationBitmap(
|
|
1730
|
+
uri = uri,
|
|
1731
|
+
getCurrentUri = { fourCornersImageUri },
|
|
1732
|
+
setCurrentUri = { fourCornersImageUri = it },
|
|
1733
|
+
setBitmap = { customFourCornersBitmap = it },
|
|
1734
|
+
reason = "fourCornersBitmap"
|
|
1735
|
+
)
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
fun applyStartPointImage(uri: String?) {
|
|
1739
|
+
updateCustomAnnotationBitmap(
|
|
1740
|
+
uri = uri,
|
|
1741
|
+
getCurrentUri = { startPointImageUri },
|
|
1742
|
+
setCurrentUri = { startPointImageUri = it },
|
|
1743
|
+
setBitmap = { customStartPointBitmap = it },
|
|
1744
|
+
reason = "startPointBitmap"
|
|
1745
|
+
)
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
fun applyWayPointImage(uri: String?) {
|
|
1749
|
+
updateCustomAnnotationBitmap(
|
|
1750
|
+
uri = uri,
|
|
1751
|
+
getCurrentUri = { wayPointImageUri },
|
|
1752
|
+
setCurrentUri = { wayPointImageUri = it },
|
|
1753
|
+
setBitmap = { customWayPointBitmap = it },
|
|
1754
|
+
reason = "wayPointBitmap"
|
|
1755
|
+
)
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
fun applyEndPointImage(uri: String?) {
|
|
1759
|
+
updateCustomAnnotationBitmap(
|
|
1760
|
+
uri = uri,
|
|
1761
|
+
getCurrentUri = { endPointImageUri },
|
|
1762
|
+
setCurrentUri = { endPointImageUri = it },
|
|
1763
|
+
setBitmap = { customEndPointBitmap = it },
|
|
1764
|
+
reason = "endPointBitmap"
|
|
1765
|
+
)
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
fun applyCustomWaypointMarkers(markers: List<Map<String, Any?>>?) {
|
|
1769
|
+
customWaypointMarkers = markers?.mapNotNull { item ->
|
|
1770
|
+
val latitude = (item["latitude"] as? Number)?.toDouble() ?: return@mapNotNull null
|
|
1771
|
+
val longitude = (item["longitude"] as? Number)?.toDouble() ?: return@mapNotNull null
|
|
1772
|
+
val rawTitle = (item["title"] as? String)?.trim()
|
|
1773
|
+
NaviCustomWaypointMarkerModel(
|
|
1774
|
+
latitude = latitude,
|
|
1775
|
+
longitude = longitude,
|
|
1776
|
+
title = rawTitle?.takeIf { it.isNotEmpty() } ?: "途经"
|
|
1777
|
+
)
|
|
1778
|
+
} ?: emptyList()
|
|
1779
|
+
refreshCustomWaypointMarkers("apply-custom-waypoint-markers")
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
private fun clearRenderedCustomWaypointMarkers() {
|
|
1783
|
+
renderedCustomWaypointMarkers.forEach { marker ->
|
|
1784
|
+
try {
|
|
1785
|
+
marker.remove()
|
|
1786
|
+
} catch (_: Throwable) {
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
renderedCustomWaypointMarkers.clear()
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
private fun refreshCustomWaypointMarkers(reason: String) {
|
|
1793
|
+
Handler(Looper.getMainLooper()).post {
|
|
1794
|
+
clearRenderedCustomWaypointMarkers()
|
|
1795
|
+
if (isDestroyed || customWaypointMarkers.isEmpty()) {
|
|
1796
|
+
return@post
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
val map = try {
|
|
1800
|
+
naviView.map
|
|
1801
|
+
} catch (error: Throwable) {
|
|
1802
|
+
Log.w("ExpoGaodeMapNaviView", "Failed to access AMap for custom waypoint markers: $reason", error)
|
|
1803
|
+
null
|
|
1804
|
+
} ?: return@post
|
|
1805
|
+
|
|
1806
|
+
customWaypointMarkers.forEach { marker ->
|
|
1807
|
+
if (marker.arrived) {
|
|
1808
|
+
return@forEach
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
val bitmap = createCustomWaypointBubbleBitmap(marker.title)
|
|
1812
|
+
val options = MarkerOptions()
|
|
1813
|
+
.position(LatLng(marker.latitude, marker.longitude))
|
|
1814
|
+
.anchor(0.5f, 1f)
|
|
1815
|
+
.zIndex(130f)
|
|
1816
|
+
.icon(BitmapDescriptorFactory.fromBitmap(bitmap))
|
|
1817
|
+
val renderedMarker = map.addMarker(options)
|
|
1818
|
+
if (renderedMarker != null) {
|
|
1819
|
+
renderedCustomWaypointMarkers.add(renderedMarker)
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
private fun resetCustomWaypointMarkerArrivalState() {
|
|
1826
|
+
customWaypointMarkers = customWaypointMarkers.map { marker ->
|
|
1827
|
+
marker.copy(arrived = false)
|
|
1828
|
+
}
|
|
1829
|
+
refreshCustomWaypointMarkers("reset-custom-waypoint-arrival-state")
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
private fun markCustomWaypointArrived(rawIndex: Int) {
|
|
1833
|
+
if (customWaypointMarkers.isEmpty()) {
|
|
1834
|
+
return
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
val resolvedIndex = when {
|
|
1838
|
+
rawIndex in customWaypointMarkers.indices -> rawIndex
|
|
1839
|
+
(rawIndex - 1) in customWaypointMarkers.indices -> rawIndex - 1
|
|
1840
|
+
else -> null
|
|
1841
|
+
} ?: return
|
|
1842
|
+
|
|
1843
|
+
customWaypointMarkers = customWaypointMarkers.mapIndexed { index, marker ->
|
|
1844
|
+
if (index == resolvedIndex) marker.copy(arrived = true) else marker
|
|
1845
|
+
}
|
|
1846
|
+
refreshCustomWaypointMarkers("arrived-waypoint-$rawIndex")
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
private fun createCustomWaypointBubbleBitmap(title: String): Bitmap {
|
|
1850
|
+
val density = context.resources.displayMetrics.density
|
|
1851
|
+
val fontSize = 16f * density
|
|
1852
|
+
val horizontalPadding = 14f * density
|
|
1853
|
+
val bodyHeight = 34f * density
|
|
1854
|
+
val strokeWidth = 2.5f * density
|
|
1855
|
+
|
|
1856
|
+
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1857
|
+
color = Color.WHITE
|
|
1858
|
+
textSize = fontSize
|
|
1859
|
+
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
|
1860
|
+
textAlign = Paint.Align.CENTER
|
|
1861
|
+
}
|
|
1862
|
+
val bodyWidth = maxOf(
|
|
1863
|
+
62f * density,
|
|
1864
|
+
textPaint.measureText(title) + horizontalPadding * 2
|
|
1865
|
+
)
|
|
1866
|
+
val width = bodyWidth.roundToInt()
|
|
1867
|
+
val height = (bodyHeight + 2f * density).roundToInt()
|
|
1868
|
+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
1869
|
+
val canvas = Canvas(bitmap)
|
|
1870
|
+
|
|
1871
|
+
val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1872
|
+
color = Color.parseColor("#2F67FF")
|
|
1873
|
+
style = Paint.Style.FILL
|
|
1874
|
+
}
|
|
1875
|
+
val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1876
|
+
color = Color.WHITE
|
|
1877
|
+
style = Paint.Style.STROKE
|
|
1878
|
+
this.strokeWidth = strokeWidth
|
|
1879
|
+
}
|
|
1880
|
+
val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1881
|
+
color = Color.parseColor("#2D15357F")
|
|
1882
|
+
style = Paint.Style.FILL
|
|
1883
|
+
setShadowLayer(6f * density, 0f, 3f * density, Color.parseColor("#2D15357F"))
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
val bodyRect = RectF(
|
|
1887
|
+
strokeWidth,
|
|
1888
|
+
strokeWidth,
|
|
1889
|
+
width - strokeWidth,
|
|
1890
|
+
bodyHeight
|
|
1891
|
+
)
|
|
1892
|
+
val cornerRadius = 17f * density
|
|
1893
|
+
canvas.drawRoundRect(bodyRect, cornerRadius, cornerRadius, shadowPaint)
|
|
1894
|
+
canvas.drawRoundRect(bodyRect, cornerRadius, cornerRadius, fillPaint)
|
|
1895
|
+
canvas.drawRoundRect(bodyRect, cornerRadius, cornerRadius, strokePaint)
|
|
1896
|
+
|
|
1897
|
+
val textY = bodyRect.centerY() - (textPaint.descent() + textPaint.ascent()) / 2f
|
|
1898
|
+
canvas.drawText(title, bodyRect.centerX(), textY, textPaint)
|
|
1899
|
+
return bitmap
|
|
1900
|
+
}
|
|
1901
|
+
|
|
908
1902
|
|
|
909
1903
|
/**
|
|
910
1904
|
* 设置是否显示交通信号灯
|
|
@@ -936,8 +1930,13 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
936
1930
|
showRouteStartIcon: Boolean,
|
|
937
1931
|
showRouteEndIcon: Boolean
|
|
938
1932
|
) {
|
|
1933
|
+
routeMarkerShowStartEndVia = showStartEndVia
|
|
1934
|
+
routeMarkerShowFootFerry = showFootFerry
|
|
1935
|
+
routeMarkerShowForbidden = showForbidden
|
|
1936
|
+
routeMarkerShowRouteStartIcon = showRouteStartIcon
|
|
1937
|
+
routeMarkerShowRouteEndIcon = showRouteEndIcon
|
|
939
1938
|
try {
|
|
940
|
-
|
|
1939
|
+
applyRouteMarkerVisibleFromState()
|
|
941
1940
|
Log.d("ExpoGaodeMapNaviView", "Route marker visibility set - startEnd:$showStartEndVia, ferry:$showFootFerry, forbidden:$showForbidden, routeStart:$showRouteStartIcon, routeEnd:$showRouteEndIcon")
|
|
942
1941
|
} catch (e: Exception) {
|
|
943
1942
|
Log.e("ExpoGaodeMapNaviView", "Failed to set route marker visibility", e)
|
|
@@ -985,9 +1984,9 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
985
1984
|
fun applyNaviArrowVisible(visible: Boolean) {
|
|
986
1985
|
try {
|
|
987
1986
|
isNaviArrowVisible = visible
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1987
|
+
commitViewOptions { options ->
|
|
1988
|
+
options.isNaviArrowVisible = visible
|
|
1989
|
+
}
|
|
991
1990
|
Log.d("ExpoGaodeMapNaviView", "Navi arrow visibility set to: $visible")
|
|
992
1991
|
} catch (e: Exception) {
|
|
993
1992
|
Log.e("ExpoGaodeMapNaviView", "Failed to set navi arrow visibility", e)
|
|
@@ -996,9 +1995,9 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
996
1995
|
|
|
997
1996
|
override fun onAttachedToWindow() {
|
|
998
1997
|
super.onAttachedToWindow()
|
|
1998
|
+
registerActiveView()
|
|
999
1999
|
try {
|
|
1000
|
-
|
|
1001
|
-
Log.d("ExpoGaodeMapNaviView", "NaviView resumed")
|
|
2000
|
+
onResume()
|
|
1002
2001
|
} catch (e: Exception) {
|
|
1003
2002
|
Log.e("ExpoGaodeMapNaviView", "Error resuming navi view", e)
|
|
1004
2003
|
}
|
|
@@ -1007,42 +2006,57 @@ class ExpoGaodeMapNaviView(context: Context, appContext: AppContext) : ExpoView(
|
|
|
1007
2006
|
override fun onDetachedFromWindow() {
|
|
1008
2007
|
super.onDetachedFromWindow()
|
|
1009
2008
|
try {
|
|
1010
|
-
|
|
1011
|
-
naviView.onDestroy()
|
|
1012
|
-
|
|
1013
|
-
// 停止语音播报
|
|
1014
|
-
aMapNavi?.stopSpeak()
|
|
1015
|
-
|
|
1016
|
-
// 移除监听器但保留 AMapNavi 实例(因为是单例)
|
|
1017
|
-
aMapNavi?.removeAMapNaviListener(naviListener)
|
|
1018
|
-
|
|
1019
|
-
Log.d("ExpoGaodeMapNaviView", "NaviView paused and destroyed")
|
|
2009
|
+
onPause()
|
|
1020
2010
|
} catch (e: Exception) {
|
|
1021
|
-
Log.e("ExpoGaodeMapNaviView", "Error
|
|
2011
|
+
Log.e("ExpoGaodeMapNaviView", "Error pausing navi view", e)
|
|
1022
2012
|
}
|
|
1023
2013
|
}
|
|
1024
2014
|
|
|
1025
2015
|
// 生命周期方法(供外部调用)
|
|
1026
2016
|
fun onResume() {
|
|
2017
|
+
if (isDestroyed) {
|
|
2018
|
+
return
|
|
2019
|
+
}
|
|
1027
2020
|
try {
|
|
1028
2021
|
naviView.onResume()
|
|
2022
|
+
refreshNaviUILayout("onResume")
|
|
2023
|
+
Log.d("ExpoGaodeMapNaviView", "NaviView resumed")
|
|
1029
2024
|
} catch (e: Exception) {
|
|
1030
2025
|
Log.e("ExpoGaodeMapNaviView", "Error resuming navi view", e)
|
|
1031
2026
|
}
|
|
1032
2027
|
}
|
|
1033
2028
|
|
|
1034
2029
|
fun onPause() {
|
|
2030
|
+
if (isDestroyed) {
|
|
2031
|
+
return
|
|
2032
|
+
}
|
|
1035
2033
|
try {
|
|
1036
2034
|
naviView.onPause()
|
|
2035
|
+
Log.d("ExpoGaodeMapNaviView", "NaviView paused")
|
|
1037
2036
|
} catch (e: Exception) {
|
|
1038
2037
|
Log.e("ExpoGaodeMapNaviView", "Error pausing navi view", e)
|
|
1039
2038
|
}
|
|
1040
2039
|
}
|
|
1041
2040
|
|
|
1042
2041
|
fun onDestroy() {
|
|
2042
|
+
if (isDestroyed) {
|
|
2043
|
+
return
|
|
2044
|
+
}
|
|
2045
|
+
isDestroyed = true
|
|
2046
|
+
isNavigationRunning = false
|
|
2047
|
+
isHostActivityInBackground = false
|
|
2048
|
+
latestNavigationNotificationSnapshot = null
|
|
2049
|
+
NavigationForegroundService.stop(context)
|
|
2050
|
+
unregisterActiveView()
|
|
2051
|
+
clearRenderedCustomWaypointMarkers()
|
|
1043
2052
|
try {
|
|
2053
|
+
naviView.onPause()
|
|
1044
2054
|
naviView.onDestroy()
|
|
2055
|
+
aMapNavi?.stopSpeak()
|
|
1045
2056
|
aMapNavi?.removeAMapNaviListener(naviListener)
|
|
2057
|
+
deleteCachedIconFile(cachedTurnIconImageUri)
|
|
2058
|
+
cachedTurnIconImageUri = null
|
|
2059
|
+
cachedTurnIconContentHash = null
|
|
1046
2060
|
// AMapNavi 是单例,不需要手动 destroy
|
|
1047
2061
|
} catch (e: Exception) {
|
|
1048
2062
|
Log.e("ExpoGaodeMapNaviView", "Error destroying navi view", e)
|