expo-gaode-map 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +19 -3
  2. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +1 -1
  3. package/android/src/main/java/expo/modules/gaodemap/managers/OverlayManager.kt +24 -6
  4. package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +28 -2
  5. package/android/src/main/java/expo/modules/gaodemap/overlays/CircleView.kt +6 -3
  6. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +317 -2
  7. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerViewModule.kt +16 -0
  8. package/android/src/main/java/expo/modules/gaodemap/utils/ColorParser.kt +25 -0
  9. package/build/components/overlays/Circle.d.ts +2 -1
  10. package/build/components/overlays/Circle.d.ts.map +1 -1
  11. package/build/components/overlays/Circle.js +39 -0
  12. package/build/components/overlays/Circle.js.map +1 -1
  13. package/build/components/overlays/Marker.d.ts.map +1 -1
  14. package/build/components/overlays/Marker.js +46 -1
  15. package/build/components/overlays/Marker.js.map +1 -1
  16. package/build/types/location.types.d.ts +4 -0
  17. package/build/types/location.types.d.ts.map +1 -1
  18. package/build/types/location.types.js.map +1 -1
  19. package/build/types/map-view.types.d.ts +2 -1
  20. package/build/types/map-view.types.d.ts.map +1 -1
  21. package/build/types/map-view.types.js.map +1 -1
  22. package/build/types/overlays.types.d.ts +20 -1
  23. package/build/types/overlays.types.d.ts.map +1 -1
  24. package/build/types/overlays.types.js.map +1 -1
  25. package/docs/API.en.md +14 -4
  26. package/docs/API.md +52 -4
  27. package/docs/EXAMPLES.en.md +58 -1
  28. package/docs/EXAMPLES.md +208 -1
  29. package/ios/ExpoGaodeMapView.swift +36 -5
  30. package/ios/ExpoGaodeMapViewModule.swift +1 -1
  31. package/ios/managers/UIManager.swift +32 -4
  32. package/ios/overlays/CircleViewModule.swift +0 -2
  33. package/ios/overlays/MarkerView.swift +205 -7
  34. package/ios/overlays/MarkerViewModule.swift +8 -2
  35. package/ios/overlays/PolygonViewModule.swift +0 -2
  36. package/ios/overlays/PolylineViewModule.swift +0 -2
  37. package/ios/utils/ColorParser.swift +45 -0
  38. package/package.json +3 -2
  39. package/src/components/overlays/Circle.tsx +48 -0
  40. package/src/components/overlays/Marker.tsx +68 -1
  41. package/src/types/location.types.ts +5 -0
  42. package/src/types/map-view.types.ts +2 -1
  43. package/src/types/overlays.types.ts +23 -1
@@ -452,7 +452,7 @@ await mapRef.current?.removeCircle('circle1');
452
452
 
453
453
  ### Marker
454
454
 
455
- **Declarative usage:**
455
+ **Declarative usage - Basic marker:**
456
456
  ```tsx
457
457
  <MapView style={{ flex: 1 }}>
458
458
  <Marker
@@ -466,6 +466,63 @@ await mapRef.current?.removeCircle('circle1');
466
466
  </MapView>
467
467
  ```
468
468
 
469
+ **Declarative usage - Custom icon:**
470
+ ```tsx
471
+ import { Image } from 'react-native';
472
+
473
+ const iconUri = Image.resolveAssetSource(require('./assets/marker-icon.png')).uri;
474
+
475
+ <MapView style={{ flex: 1 }}>
476
+ <Marker
477
+ position={{ latitude: 39.9, longitude: 116.4 }}
478
+ title="Custom Icon"
479
+ icon={iconUri}
480
+ iconWidth={50}
481
+ iconHeight={50}
482
+ onPress={() => console.log('Custom icon marker pressed')}
483
+ />
484
+ </MapView>
485
+ ```
486
+
487
+ **Declarative usage - Custom view:**
488
+ ```tsx
489
+ import { View, Text, StyleSheet } from 'react-native';
490
+
491
+ <MapView style={{ flex: 1 }}>
492
+ <Marker
493
+ position={{ latitude: 39.9, longitude: 116.4 }}
494
+ customViewWidth={120}
495
+ customViewHeight={40}
496
+ onPress={() => console.log('Custom view marker pressed')}
497
+ >
498
+ <View style={styles.markerContainer}>
499
+ <Text style={styles.markerText}>Custom Content</Text>
500
+ </View>
501
+ </Marker>
502
+ </MapView>
503
+
504
+ const styles = StyleSheet.create({
505
+ markerContainer: {
506
+ backgroundColor: '#fff',
507
+ padding: 8,
508
+ borderRadius: 8,
509
+ borderWidth: 2,
510
+ borderColor: '#007AFF',
511
+ },
512
+ markerText: {
513
+ color: '#007AFF',
514
+ fontWeight: 'bold',
515
+ fontSize: 14,
516
+ },
517
+ });
518
+ ```
519
+
520
+ > **Important Notes**:
521
+ > - **Custom Icon**: Use `icon` property with `iconWidth` and `iconHeight` to control icon size
522
+ > - **Custom View**: Use `children` with `customViewWidth` and `customViewHeight` to control custom view size
523
+ > - `iconWidth/iconHeight` only apply to `icon` property, not `children`
524
+ > - `customViewWidth/customViewHeight` only apply to `children` property, not `icon`
525
+
469
526
  **Imperative usage:**
470
527
  ```tsx
471
528
  await mapRef.current?.addMarker('marker1', {
package/docs/EXAMPLES.md CHANGED
@@ -484,7 +484,7 @@ await mapRef.current?.updateMarker('marker1', {
484
484
  await mapRef.current?.removeMarker('marker1');
485
485
  ```
486
486
 
487
- > **⚠️ 限制**:命令式 API 添加的 Marker **不支持事件回调**(onPress, onDragEnd 等)。如需事件处理,请使用声明式 `<Marker>` 组件。
487
+ > **⚠️ 限制**:命令式 API 添加的 Marker **不支持事件回调**(onPress, onDragEnd 等)和**自定义视图**。如需这些功能,请使用声明式 `<Marker>` 组件。
488
488
 
489
489
  #### 自定义图标
490
490
 
@@ -511,6 +511,213 @@ const iconUri = Image.resolveAssetSource(require('./assets/marker-icon.png')).ur
511
511
  > - 在不同密度屏幕上会自动缩放,保持视觉一致性
512
512
  > - 支持网络图片(http/https)和本地图片
513
513
 
514
+ #### 自定义视图 ⭐ 推荐
515
+
516
+ 使用 `children` 属性可以完全自定义标记的外观,支持任意 React Native 组件和样式:
517
+
518
+ **基础自定义视图:**
519
+ ```tsx
520
+ import { View, Text, StyleSheet } from 'react-native';
521
+
522
+ <MapView style={{ flex: 1 }}>
523
+ <Marker
524
+ position={{ latitude: 39.9, longitude: 116.4 }}
525
+ customViewWidth={200}
526
+ customViewHeight={50}
527
+ onPress={() => Alert.alert('标记', '点击了自定义标记')}
528
+ >
529
+ <View style={styles.markerContainer}>
530
+ <Text style={styles.markerText}>北京市中心</Text>
531
+ </View>
532
+ </Marker>
533
+ </MapView>
534
+
535
+ const styles = StyleSheet.create({
536
+ markerContainer: {
537
+ backgroundColor: '#fff',
538
+ borderColor: '#2196F3',
539
+ borderWidth: 2,
540
+ borderRadius: 12,
541
+ paddingVertical: 8,
542
+ paddingHorizontal: 16,
543
+ shadowColor: '#000',
544
+ shadowOffset: { width: 0, height: 2 },
545
+ shadowOpacity: 0.25,
546
+ shadowRadius: 3.84,
547
+ elevation: 5,
548
+ },
549
+ markerText: {
550
+ color: '#2196F3',
551
+ fontSize: 14,
552
+ fontWeight: 'bold',
553
+ },
554
+ });
555
+ ```
556
+
557
+ **带图标的自定义视图:**
558
+ ```tsx
559
+ import { View, Text, Image, StyleSheet } from 'react-native';
560
+
561
+ <MapView style={{ flex: 1 }}>
562
+ <Marker
563
+ position={{ latitude: 39.9, longitude: 116.4 }}
564
+ iconWidth={150}
565
+ iconHeight={60}
566
+ >
567
+ <View style={styles.customMarker}>
568
+ <Image
569
+ source={require('./assets/location-pin.png')}
570
+ style={styles.markerIcon}
571
+ />
572
+ <View style={styles.markerContent}>
573
+ <Text style={styles.markerTitle}>北京</Text>
574
+ <Text style={styles.markerSubtitle}>中国首都</Text>
575
+ </View>
576
+ </View>
577
+ </Marker>
578
+ </MapView>
579
+
580
+ const styles = StyleSheet.create({
581
+ customMarker: {
582
+ flexDirection: 'row',
583
+ alignItems: 'center',
584
+ backgroundColor: '#4CAF50',
585
+ borderRadius: 20,
586
+ paddingVertical: 6,
587
+ paddingHorizontal: 12,
588
+ shadowColor: '#000',
589
+ shadowOffset: { width: 0, height: 2 },
590
+ shadowOpacity: 0.3,
591
+ shadowRadius: 4,
592
+ elevation: 6,
593
+ },
594
+ markerIcon: {
595
+ width: 24,
596
+ height: 24,
597
+ marginRight: 8,
598
+ },
599
+ markerContent: {
600
+ flexDirection: 'column',
601
+ },
602
+ markerTitle: {
603
+ color: '#fff',
604
+ fontSize: 14,
605
+ fontWeight: 'bold',
606
+ },
607
+ markerSubtitle: {
608
+ color: '#E8F5E9',
609
+ fontSize: 11,
610
+ },
611
+ });
612
+ ```
613
+
614
+ **动态内容标记:**
615
+ ```tsx
616
+ import { View, Text, StyleSheet } from 'react-native';
617
+
618
+ function LocationMarker({ location }: { location: Location }) {
619
+ return (
620
+ <Marker
621
+ position={{
622
+ latitude: location.latitude,
623
+ longitude: location.longitude
624
+ }}
625
+ customViewWidth={220}
626
+ customViewHeight={60}
627
+ onPress={() => Alert.alert('位置', location.address)}
628
+ >
629
+ <View style={styles.locationMarker}>
630
+ <Text style={styles.locationTitle} numberOfLines={1}>
631
+ {location.address || '当前位置'}
632
+ </Text>
633
+ <Text style={styles.locationCoords}>
634
+ {location.latitude.toFixed(6)}, {location.longitude.toFixed(6)}
635
+ </Text>
636
+ </View>
637
+ </Marker>
638
+ );
639
+ }
640
+
641
+ const styles = StyleSheet.create({
642
+ locationMarker: {
643
+ backgroundColor: '#FF5722',
644
+ borderRadius: 10,
645
+ paddingVertical: 8,
646
+ paddingHorizontal: 12,
647
+ borderLeftWidth: 4,
648
+ borderLeftColor: '#D84315',
649
+ shadowColor: '#000',
650
+ shadowOffset: { width: 0, height: 2 },
651
+ shadowOpacity: 0.25,
652
+ shadowRadius: 3.84,
653
+ elevation: 5,
654
+ },
655
+ locationTitle: {
656
+ color: '#fff',
657
+ fontSize: 13,
658
+ fontWeight: '600',
659
+ marginBottom: 2,
660
+ },
661
+ locationCoords: {
662
+ color: '#FFCCBC',
663
+ fontSize: 10,
664
+ },
665
+ });
666
+ ```
667
+
668
+ **价格标签样式:**
669
+ ```tsx
670
+ <Marker
671
+ position={{ latitude: 39.9, longitude: 116.4 }}
672
+ iconWidth={80}
673
+ iconHeight={40}
674
+ >
675
+ <View style={styles.priceTag}>
676
+ <Text style={styles.priceText}>¥1280</Text>
677
+ <View style={styles.priceArrow} />
678
+ </View>
679
+ </Marker>
680
+
681
+ const styles = StyleSheet.create({
682
+ priceTag: {
683
+ backgroundColor: '#FF9800',
684
+ borderRadius: 8,
685
+ paddingVertical: 6,
686
+ paddingHorizontal: 12,
687
+ position: 'relative',
688
+ },
689
+ priceText: {
690
+ color: '#fff',
691
+ fontSize: 16,
692
+ fontWeight: 'bold',
693
+ },
694
+ priceArrow: {
695
+ position: 'absolute',
696
+ bottom: -6,
697
+ left: '50%',
698
+ marginLeft: -6,
699
+ width: 0,
700
+ height: 0,
701
+ borderLeftWidth: 6,
702
+ borderRightWidth: 6,
703
+ borderTopWidth: 6,
704
+ borderStyle: 'solid',
705
+ borderLeftColor: 'transparent',
706
+ borderRightColor: 'transparent',
707
+ borderTopColor: '#FF9800',
708
+ },
709
+ });
710
+ ```
711
+
712
+ > **自定义视图要点**:
713
+ > - ✅ 支持所有 React Native 样式(backgroundColor、borderRadius、flexbox、shadow 等)
714
+ > - ✅ 使用 `iconWidth` 和 `iconHeight` 控制最终显示尺寸
715
+ > - ✅ 子视图会自动转换为图片显示在地图上
716
+ > - ✅ 支持动态内容和复杂布局
717
+ > - ⚠️ 仅支持声明式 `<Marker>` 组件
718
+ > - ⚠️ 建议明确指定 `iconWidth` 和 `iconHeight` 以确保跨设备一致性
719
+ > - ⚠️ iOS 的 shadow 样式可能需要额外配置(shadowColor、shadowOffset 等)
720
+
514
721
  #### Android 特有属性
515
722
 
516
723
  ```tsx
@@ -59,6 +59,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate {
59
59
  let onMapPress = EventDispatcher()
60
60
  let onMapLongPress = EventDispatcher()
61
61
  let onLoad = EventDispatcher()
62
+ let onLocation = EventDispatcher()
62
63
  let onMarkerPress = EventDispatcher()
63
64
  let onMarkerDragStart = EventDispatcher()
64
65
  let onMarkerDrag = EventDispatcher()
@@ -99,6 +100,17 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate {
99
100
 
100
101
  cameraManager = CameraManager(mapView: mapView)
101
102
  uiManager = UIManager(mapView: mapView)
103
+
104
+ // 设置定位变化回调
105
+ uiManager.onLocationChanged = { [weak self] latitude, longitude, accuracy in
106
+ self?.onLocation([
107
+ "latitude": latitude,
108
+ "longitude": longitude,
109
+ "accuracy": accuracy,
110
+ "timestamp": Date().timeIntervalSince1970 * 1000
111
+ ])
112
+ }
113
+
102
114
  overlayManager = OverlayManager(mapView: mapView)
103
115
 
104
116
  // 设置覆盖物点击回调
@@ -118,6 +130,20 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate {
118
130
  override func layoutSubviews() {
119
131
  super.layoutSubviews()
120
132
  mapView.frame = bounds
133
+
134
+ // 收集并设置 MarkerView
135
+ collectAndSetupMarkerViews()
136
+ }
137
+
138
+ /**
139
+ * 收集所有 MarkerView 子视图并设置地图
140
+ */
141
+ private func collectAndSetupMarkerViews() {
142
+ for subview in subviews {
143
+ if let markerView = subview as? MarkerView {
144
+ markerView.setMap(mapView)
145
+ }
146
+ }
121
147
  }
122
148
 
123
149
  /**
@@ -125,13 +151,15 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate {
125
151
  * 将地图实例传递给覆盖物子视图
126
152
  */
127
153
  override func addSubview(_ view: UIView) {
128
- super.addSubview(view)
129
-
130
-
131
-
132
154
  if let markerView = view as? MarkerView {
155
+ // 不添加到视图层级,只调用 setMap
133
156
  markerView.setMap(mapView)
134
- } else if let circleView = view as? CircleView {
157
+ return
158
+ }
159
+
160
+ super.addSubview(view)
161
+
162
+ if let circleView = view as? CircleView {
135
163
  circleView.setMap(mapView)
136
164
  } else if let polylineView = view as? PolylineView {
137
165
  polylineView.setMap(mapView)
@@ -186,6 +214,9 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate {
186
214
  uiManager.setShowsTraffic(showsTraffic)
187
215
  uiManager.setShowsBuildings(showsBuildings)
188
216
  uiManager.setShowsIndoorMap(showsIndoorMap)
217
+
218
+ // 收集并设置所有 MarkerView
219
+ collectAndSetupMarkerViews()
189
220
  }
190
221
 
191
222
  // MARK: - 缩放控制
@@ -9,7 +9,7 @@ public class ExpoGaodeMapViewModule: Module {
9
9
  Name("ExpoGaodeMapView")
10
10
 
11
11
  View(ExpoGaodeMapView.self) {
12
- Events("onMapPress", "onMapLongPress", "onLoad", "onMarkerPress", "onMarkerDragStart", "onMarkerDrag", "onMarkerDragEnd", "onCirclePress", "onPolygonPress", "onPolylinePress")
12
+ Events("onMapPress", "onMapLongPress", "onLoad", "onLocation", "onMarkerPress", "onMarkerDragStart", "onMarkerDrag", "onMarkerDragEnd", "onCirclePress", "onPolygonPress", "onPolylinePress")
13
13
 
14
14
  Prop("mapType") { (view: ExpoGaodeMapView, type: Int) in
15
15
  view.mapType = type
@@ -10,12 +10,16 @@ import MAMapKit
10
10
  * - 图层显示管理
11
11
  * - 用户位置样式配置
12
12
  */
13
- class UIManager {
13
+ class UIManager: NSObject, MAMapViewDelegate {
14
14
  /// 弱引用地图视图,避免循环引用
15
15
  private weak var mapView: MAMapView?
16
16
 
17
+ /// 定位变化回调
18
+ var onLocationChanged: ((_ latitude: Double, _ longitude: Double, _ accuracy: Double) -> Void)?
19
+
17
20
  init(mapView: MAMapView) {
18
21
  self.mapView = mapView
22
+ super.init()
19
23
  }
20
24
 
21
25
  // MARK: - 地图类型
@@ -97,14 +101,38 @@ class UIManager {
97
101
  */
98
102
  func setShowsUserLocation(_ show: Bool, followUser: Bool) {
99
103
  guard let mapView = mapView else { return }
100
- mapView.showsUserLocation = show
101
- if show && followUser {
102
- mapView.userTrackingMode = .follow
104
+
105
+ if show {
106
+ // 设置代理以监听定位更新
107
+ if mapView.delegate == nil {
108
+ mapView.delegate = self
109
+ }
110
+ mapView.showsUserLocation = true
111
+ if followUser {
112
+ mapView.userTrackingMode = .follow
113
+ } else {
114
+ mapView.userTrackingMode = .none
115
+ }
103
116
  } else {
117
+ mapView.showsUserLocation = false
104
118
  mapView.userTrackingMode = .none
105
119
  }
106
120
  }
107
121
 
122
+ // MARK: - MAMapViewDelegate
123
+
124
+ /**
125
+ * 定位更新回调
126
+ */
127
+ public func mapView(_ mapView: MAMapView, didUpdate userLocation: MAUserLocation, updatingLocation: Bool) {
128
+ guard updatingLocation, let location = userLocation.location else { return }
129
+ onLocationChanged?(
130
+ location.coordinate.latitude,
131
+ location.coordinate.longitude,
132
+ location.horizontalAccuracy
133
+ )
134
+ }
135
+
108
136
  /**
109
137
  * 设置用户位置样式
110
138
  * @param config 样式配置字典
@@ -5,8 +5,6 @@ public class CircleViewModule: Module {
5
5
  Name("CircleView")
6
6
 
7
7
  View(CircleView.self) {
8
- Events("onPress")
9
-
10
8
  Prop("center") { (view: CircleView, center: [String: Double]) in
11
9
  view.setCenter(center)
12
10
  }