Browse Source

[new]地图增加moveCameraToBounds、updateOrAddCircle方法

zk 3 months ago
parent
commit
98502a86a6
34 changed files with 915 additions and 78 deletions
  1. BIN
      assets/images/icon_amap_logo.webp
  2. 1 0
      assets/string/base/string.xml
  3. 133 0
      lib/module/commonpoint/select_address/common_point_select_address_controller.dart
  4. 51 21
      lib/module/commonpoint/select_address/common_point_select_address_page.dart
  5. 5 0
      lib/resource/assets.gen.dart
  6. 3 0
      lib/resource/string.gen.dart
  7. 8 9
      plugins/map/lib/flutter_map.dart
  8. 8 0
      plugins/map/lib/src/consts/camera_change_type.dart
  9. 49 0
      plugins/map/lib/src/consts/circle_options.dart
  10. 17 3
      plugins/map/lib/src/consts/map_constants.dart
  11. 0 1
      plugins/map/lib/src/core/flutter_map.dart
  12. 0 1
      plugins/map/lib/src/core/map_base_controller.dart
  13. 94 17
      plugins/map/lib/src/core/map_controller_impl.dart
  14. 1 0
      plugins/map/lib/src/core/map_platform.dart
  15. 20 0
      plugins/map/lib/src/entity/camera_change.dart
  16. 1 1
      plugins/map/lib/src/entity/camera_position.dart
  17. 23 0
      plugins/map/lib/src/entity/latlng_bounds.dart
  18. 8 0
      plugins/map/lib/src/interface/handle/map_handle_interface.dart
  19. 1 3
      plugins/map/lib/src/interface/map_sdk_interface.dart
  20. 7 2
      plugins/map/lib/src/interface/overlays/map_face_interface.dart
  21. 32 0
      plugins/map/lib/src/utils/camera_update_util.dart
  22. 1 1
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/FlutterViewPlugin.java
  23. 4 1
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/MapAmapAndroidPlugin.java
  24. 24 9
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/amap/AmapView.java
  25. 94 2
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/amap/MapController.java
  26. 5 4
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/amap/MapViewFactory.java
  27. 17 2
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/contants/Constants.java
  28. 85 0
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/event/FlutterCameraChangeEventPlugin.java
  29. 1 1
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/FlutterLocationEventPlugin.java
  30. 2 0
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/MyMethodCallHandler.java
  31. 122 0
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/face/FaceController.java
  32. 88 0
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/face/FlutterCircleOptions.java
  33. 5 0
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/marker/MarkersController.java
  34. 5 0
      plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/polyline/PolylineController.java

BIN
assets/images/icon_amap_logo.webp


+ 1 - 0
assets/string/base/string.xml

@@ -446,4 +446,5 @@
     <string name="select_address_commonly_used_range">常用地范围</string>
     <string name="select_address_please_enter_place_name">请输入地方名称</string>
     <string name="select_address_current_location">[当前位置]</string>
+    <string name="location_amap_co">合作单位:高德软件有限公司 审图号 GS(2021)6375号</string>
 </resources>

+ 133 - 0
lib/module/commonpoint/select_address/common_point_select_address_controller.dart

@@ -1,15 +1,27 @@
 import 'package:flutter/cupertino.dart';
+import 'package:flutter_map/flutter_map.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
 import 'package:get/get_core/src/get_main.dart';
 import 'package:injectable/injectable.dart';
 import 'package:location/base/base_controller.dart';
+import 'package:location/sdk/map/map_helper.dart';
+import 'package:permission_handler/permission_handler.dart';
 import 'package:sliding_sheet2/sliding_sheet2.dart';
 
+import '../../../dialog/common_confirm_dialog_impl.dart';
+import '../../../dialog/location_permission_dialog.dart';
+import '../../../resource/string.gen.dart';
+import '../../../utils/permission_util.dart';
+import '../../../utils/toast_util.dart';
+
 @injectable
 class CommonPointSelectAddressController extends BaseController {
   final TextEditingController searchEditController = TextEditingController();
   final FocusNode searchFocusNode = FocusNode();
 
+  final mapController = MapController.create();
+
   final SheetController sheetController = SheetController();
 
   final RxDouble _commonPointRange = RxDouble(200);
@@ -20,6 +32,51 @@ class CommonPointSelectAddressController extends BaseController {
 
   double get sheetProgress => _sheetProgress.value;
 
+  bool _isRangeChanging = false;
+
+  bool _isFirstMoveCamera = true;
+
+  final MapPadding mapPadding =
+      MapPadding(top: 80, left: 80, right: 80, bottom: 80);
+
+  final CircleOptions circleOptions = CircleOptions(
+      fillColor: '#4D7B7DFF', strokeWidth: 2.w, strokeColor: '#92AEE6');
+
+  @override
+  void onReady() {
+    super.onReady();
+    _updateCurrentLocation();
+    mapController.setOnCameraChangeListener(
+      (camera) {
+        if (_isRangeChanging) {
+          return;
+        }
+        if (camera.function == CameraChangeType.onCameraChange) {
+          //暂时隐藏范围
+          if (circleOptions.visible == true) {
+            circleOptions.visible = false;
+            _updateOrAddCircle();
+          }
+        } else {
+          circleOptions
+            ..radius = commonPointRange
+            ..longitude = camera.position.longitude
+            ..visible = true
+            ..latitude = camera.position.latitude;
+          _updateOrAddCircle();
+          if (_isFirstMoveCamera) {
+            _isFirstMoveCamera = false;
+            _moveCameraToBounds();
+          }
+        }
+      },
+    );
+  }
+
+  void _updateOrAddCircle() {
+    mapController.updateOrAddCircle('DC', circleOptions);
+  }
+
   void backClick() {
     if (sheetController.state?.isExpanded == true) {
       sheetController.snapToExtent(0,
@@ -31,6 +88,19 @@ class CommonPointSelectAddressController extends BaseController {
 
   void setCommonPointRange(double value) {
     _commonPointRange.value = value;
+    circleOptions.radius = value;
+    if (circleOptions.visible == true) {
+      _updateOrAddCircle();
+      _moveCameraToBounds();
+    }
+  }
+
+  _moveCameraToBounds() {
+    final latLngBounds = CameraUpdateUtil.getBounds(circleOptions.latitude,
+        circleOptions.longitude, circleOptions.radius ?? 0);
+    if (latLngBounds != null) {
+      mapController.moveCameraToBounds(latLngBounds, mapPadding: mapPadding);
+    }
   }
 
   setSheetProgress(double progress) {
@@ -60,4 +130,67 @@ class CommonPointSelectAddressController extends BaseController {
     await Future.delayed(Duration(milliseconds: 150));
     searchFocusNode.requestFocus();
   }
+
+  void setCommonPointRangeStart() {
+    _isRangeChanging = true;
+  }
+
+  void setCommonPointRangeEnd() {
+    _isRangeChanging = false;
+  }
+
+  void moveToCurrentLocation() async {
+    //权限检查
+    bool isGranted = await PermissionUtil.checkLocationPermission();
+    if (!isGranted) {
+      LocationPermissionDialog.show(onNextStep: _requestLocationPermission);
+    } else {
+      _updateCurrentLocation();
+    }
+  }
+
+  void _updateCurrentLocation() {
+    var lastLocation = MapHelper.getLastLocation();
+    if (lastLocation == null) {
+      locationListener(MapLocation location) {
+        MapHelper.removeLocationListener(locationListener);
+        _showCurrentRange(location);
+      }
+
+      MapHelper.addLocationListener(locationListener);
+    } else {
+      _showCurrentRange(lastLocation);
+    }
+  }
+
+  void _showCurrentRange(MapLocation location) {
+    circleOptions.latitude = location.latitude;
+    circleOptions.longitude = location.longitude;
+    _moveCameraToBounds();
+  }
+
+  void _requestLocationPermission() async {
+    bool isGranted = await PermissionUtil.requestLocationPermission();
+    if (isGranted) {
+      _showLocationAlways();
+      _updateCurrentLocation();
+    } else {
+      permissionRefuseDialog(settingClick: () {
+        openAppSettings();
+      });
+      ToastUtil.show(StringName.permissionRequestFail);
+    }
+  }
+
+  void _showLocationAlways() async {
+    bool isGranted = await PermissionUtil.checkShowLocationAlways();
+    if (!isGranted) {
+      LocationAlwaysPermissionDialog.show(onNextStep: () async {
+        isGranted = await PermissionUtil.requestShowLocationAlways();
+        if (isGranted) {
+          _updateCurrentLocation();
+        }
+      });
+    }
+  }
 }

+ 51 - 21
lib/module/commonpoint/select_address/common_point_select_address_page.dart

@@ -13,7 +13,6 @@ import 'package:location/resource/colors.gen.dart';
 import 'package:location/resource/string.gen.dart';
 import 'package:location/utils/common_expand.dart';
 import 'package:sliding_sheet2/sliding_sheet2.dart';
-
 import '../../../router/app_pages.dart';
 import '../../../widget/common_view.dart';
 import 'common_point_select_address_controller.dart';
@@ -43,7 +42,7 @@ class CommonPointSelectAddressPage
       },
       child: Stack(
         children: [
-          MapWidget(),
+          buildMapView(),
           buildHeadBgView(),
           buildMapLogoView(),
           buildLocationFunView(),
@@ -61,25 +60,28 @@ class CommonPointSelectAddressPage
         bottom: 0.48.sh + controller.sheetProgress * 0.48.sh,
         child: Opacity(
           opacity: 1 - controller.sheetProgress,
-          child: Container(
-              width: 38.w,
-              height: 38.w,
-              decoration: BoxDecoration(
-                color: ColorName.white,
-                borderRadius: BorderRadius.circular(10.r),
-                boxShadow: [
-                  BoxShadow(
-                    color: ColorName.black10,
-                    blurRadius: 16,
-                    spreadRadius: 2,
-                    offset: Offset(0, 4.w),
-                  ),
-                ],
-              ),
-              child: Center(
-                child: Assets.images.iconCurrentLocation
-                    .image(width: 20.w, height: 20.w),
-              )),
+          child: GestureDetector(
+            onTap: controller.moveToCurrentLocation,
+            child: Container(
+                width: 38.w,
+                height: 38.w,
+                decoration: BoxDecoration(
+                  color: ColorName.white,
+                  borderRadius: BorderRadius.circular(10.r),
+                  boxShadow: [
+                    BoxShadow(
+                      color: ColorName.black10,
+                      blurRadius: 16,
+                      spreadRadius: 2,
+                      offset: Offset(0, 4.w),
+                    ),
+                  ],
+                ),
+                child: Center(
+                  child: Assets.images.iconCurrentLocation
+                      .image(width: 20.w, height: 20.w),
+                )),
+          ),
         ),
       );
     });
@@ -293,6 +295,12 @@ class CommonPointSelectAddressPage
                         onChanged: (value) {
                           controller.setCommonPointRange(value);
                         },
+                        onChangeStart: (value) {
+                          controller.setCommonPointRangeStart();
+                        },
+                        onChangeEnd: (value) {
+                          controller.setCommonPointRangeEnd();
+                        },
                       );
                     }),
                   ),
@@ -320,6 +328,28 @@ class CommonPointSelectAddressPage
       );
     });
   }
+
+  Widget buildMapView() {
+    return SizedBox(
+        width: double.infinity,
+        height: 0.65.sh,
+        child: Stack(
+          children: [
+            MapWidget(controller: controller.mapController),
+            Center(
+              child: Container(
+                width: 18.w,
+                height: 18.w,
+                decoration: BoxDecoration(
+                  shape: BoxShape.circle,
+                  color: ColorName.colorPrimary,
+                  border: Border.all(color: ColorName.white, width: 2.w),
+                ),
+              ),
+            )
+          ],
+        ));
+  }
 }
 
 class CustomTrackShape extends RoundedRectSliderTrackShape {

+ 5 - 0
lib/resource/assets.gen.dart

@@ -147,6 +147,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconAlipayScanPayment =>
       const AssetGenImage('assets/images/icon_alipay_scan_payment.webp');
 
+  /// File path: assets/images/icon_amap_logo.webp
+  AssetGenImage get iconAmapLogo =>
+      const AssetGenImage('assets/images/icon_amap_logo.webp');
+
   /// File path: assets/images/icon_apple_recover_subscribe.webp
   AssetGenImage get iconAppleRecoverSubscribe =>
       const AssetGenImage('assets/images/icon_apple_recover_subscribe.webp');
@@ -840,6 +844,7 @@ class $AssetsImagesGen {
         iconAgreementClose,
         iconAlipayPayment,
         iconAlipayScanPayment,
+        iconAmapLogo,
         iconAppleRecoverSubscribe,
         iconAvatarClose,
         iconAvatarSelected,

+ 3 - 0
lib/resource/string.gen.dart

@@ -391,6 +391,8 @@ class StringName {
       'select_address_please_enter_place_name'.tr; // 请输入地方名称
   static String get selectAddressCurrentLocation =>
       'select_address_current_location'.tr; // [当前位置]
+  static String get locationAmapCo =>
+      'location_amap_co'.tr; // 合作单位:高德软件有限公司 审图号 GS(2021)6375号
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -767,6 +769,7 @@ class StringMultiSource {
       'select_address_commonly_used_range': '常用地范围',
       'select_address_please_enter_place_name': '请输入地方名称',
       'select_address_current_location': '[当前位置]',
+      'location_amap_co': '合作单位:高德软件有限公司 审图号 GS(2021)6375号',
     },
   };
 }

+ 8 - 9
plugins/map/lib/flutter_map.dart

@@ -1,16 +1,18 @@
 library flutter_map;
 
+//初始化
+export 'package:flutter_map/src/core/flutter_map.dart';
+
 //地图组件
 export 'package:flutter_map/src/widget/map_widget.dart';
 export 'package:flutter_map/src/core/map_base_controller.dart';
 
-//初始化
-export 'package:flutter_map/src/core/flutter_map.dart';
-
-//公共配置.
+//参数配置
 export 'package:flutter_map/src/consts/map_constants.dart';
 export 'package:flutter_map/src/consts/marker_type.dart';
 export 'package:flutter_map/src/consts/polyline_type.dart';
+export 'package:flutter_map/src/consts/circle_options.dart';
+export 'package:flutter_map/src/consts/camera_change_type.dart';
 
 //实体类
 export 'package:flutter_map/src/entity/marker.dart';
@@ -22,9 +24,6 @@ export 'package:flutter_map/src/entity/map_padding.dart';
 export 'package:flutter_map/src/entity/popup.dart';
 export 'package:flutter_map/src/entity/polyline.dart';
 
-//接口
-export 'package:flutter_map/src/interface/map_sdk_interface.dart';
-export 'package:flutter_map/src/interface/overlays/map_marker_interface.dart';
-
 //工具类
-export 'package:flutter_map/src/utils/map_util.dart';
+export 'package:flutter_map/src/utils/map_util.dart';
+export 'package:flutter_map/src/utils/camera_update_util.dart';

+ 8 - 0
plugins/map/lib/src/consts/camera_change_type.dart

@@ -0,0 +1,8 @@
+enum CameraChangeType {
+  onCameraChange('onCameraChange'),
+  onCameraChangeFinish('onCameraChangeFinish');
+
+  final String value;
+
+  const CameraChangeType(this.value);
+}

+ 49 - 0
plugins/map/lib/src/consts/circle_options.dart

@@ -0,0 +1,49 @@
+class CircleOptions {
+  double? latitude;
+
+  double? longitude;
+
+  double? radius;
+
+  String? strokeColor;
+
+  String? fillColor;
+
+  double? strokeWidth;
+
+  bool? visible;
+
+  CircleOptions({
+    this.latitude,
+    this.longitude,
+    this.radius,
+    this.strokeColor,
+    this.fillColor,
+    this.strokeWidth,
+    this.visible,
+  });
+
+  Map<String, dynamic> toJson() {
+    return {
+      'latitude': latitude,
+      'longitude': longitude,
+      'radius': radius,
+      'strokeColor': strokeColor,
+      'fillColor': fillColor,
+      'strokeWidth': strokeWidth,
+      'visible': visible,
+    };
+  }
+
+  factory CircleOptions.fromJson(Map<String, dynamic> map) {
+    return CircleOptions(
+      latitude: map['latitude'],
+      longitude: map['longitude'],
+      radius: map['radius'],
+      strokeColor: map['strokeColor'],
+      fillColor: map['fillColor'],
+      strokeWidth: map['strokeWidth'],
+      visible: map['visible'],
+    );
+  }
+}

+ 17 - 3
plugins/map/lib/src/consts/map_constants.dart

@@ -1,19 +1,27 @@
+import '../../flutter_map.dart';
+
+typedef MapLocationListener = void Function(MapLocation location);
+
 class MapConstants {
   MapConstants._();
 
-  //定位持续流通道名称
+  //定位持续流通道名称 唯一
   static const String mapLocationEventChannel =
       'atmob_map_location_event_channel';
 
-  //地图SDK通道名称
+  //地图SDK通道名称 唯一
   static const String mapMethodChannel = 'atmob_map_method_channel';
 
-  //地图widget通道
+  //地图widget通道 可多个
   static const String mapViewChannelName = "com.atmob.flutter_map/map_view_";
 
   //地图widget_viewType名称
   static const String mapWidgetViewType = "com.atmob.flutter.map";
 
+  //地图移动监听持续流通道名称 可多个
+  static const String mapCameraChangeEventChannel =
+      'com.atmob.flutter_map/camera_change_event';
+
   /// *********************************************************************************************************************
   //SDK方法名称
   static const String init = 'init';
@@ -28,6 +36,9 @@ class MapConstants {
       "map#moveToSuitableLocation";
   static const String methodInteractionEnabled = "map#interactionEnabled";
   static const String methodMapLogoVisible = "map#logoVisible";
+  static const String methodSetCameraChangeEventChannel =
+      "map#setCameraChangeEventChannel";
+  static const String methodMoveCameraToBounds = "map#moveCameraToBounds";
 
   //标记物相关方法名称
   static const String methodUpdateOrAddMarkers = "marker#updateOrAddMarkers";
@@ -42,4 +53,7 @@ class MapConstants {
   static const String methodAddPolyline = "polyline#addPolyline";
   static const String methodRemovePolyline = "polyline#removePolyline";
   static const String methodClearAllPolylines = "polyline#clearAllPolylines";
+
+  //面
+  static const String methodUpdateOrAddCircle = "face#updateOrAddCircle";
 }

+ 0 - 1
plugins/map/lib/src/core/flutter_map.dart

@@ -1,5 +1,4 @@
 import 'dart:async';
-
 import 'package:flutter/services.dart';
 import '../../flutter_map.dart';
 import 'map_platform.dart';

+ 0 - 1
plugins/map/lib/src/core/map_base_controller.dart

@@ -1,5 +1,4 @@
 import 'package:flutter/services.dart';
-import 'package:flutter_map/flutter_map.dart';
 import 'package:flutter_map/src/interface/overlays/map_marker_interface.dart';
 import 'package:flutter_map/src/interface/overlays/map_polyline_interface.dart';
 import '../interface/handle/map_handle_interface.dart';

+ 94 - 17
plugins/map/lib/src/core/map_controller_impl.dart

@@ -2,19 +2,49 @@ import 'dart:convert';
 import 'dart:io';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/services.dart';
+import 'package:flutter_map/src/consts/circle_options.dart';
 import 'package:flutter_map/src/consts/map_constants.dart';
 import 'package:flutter_map/src/entity/camera_position.dart';
 import 'package:flutter_map/src/entity/codable.dart';
 import 'package:flutter_map/src/entity/lat_lng.dart';
+import 'package:flutter_map/src/entity/latlng_bounds.dart';
 import 'package:flutter_map/src/entity/marker.dart';
 import 'package:flutter_map/src/entity/polyline.dart';
 import '../../flutter_map.dart';
+import '../entity/camera_change.dart';
 
 class MapControllerImpl implements MapController {
   final _pendingOperations = <Map<String, dynamic>>[];
   MethodChannel? _channel;
   bool _isDisposed = false;
 
+  /// iOS 需要一个Map 而不是json string
+  dynamic _encodeJson(dynamic object) {
+    // iOS平台直接返回对象或转换为JSON对象
+    if (Platform.isIOS) {
+      if (object is List) {
+        return object
+            .map((item) => item is Codable ? item.toJson() : item)
+            .toList();
+      }
+      return object is Codable ? object.toJson() : object;
+    }
+
+    // 其他平台序列化为JSON字符串
+    return jsonEncode(object);
+  }
+
+  void _executeMethod(Map<String, dynamic> op) {
+    _channel?.invokeMethod(op['method'], op['args']).then((_) {
+      if (op['invokeSuccessCallback'] != null) {
+        dynamic callback = op['invokeSuccessCallback'];
+        callback();
+      }
+    }).catchError((e) {
+      debugPrint('Method ${op['method']} failed: $e');
+    });
+  }
+
   @override
   void setChannel(MethodChannel channel) {
     if (_isDisposed) return;
@@ -26,12 +56,6 @@ class MapControllerImpl implements MapController {
     _pendingOperations.clear();
   }
 
-  void _executeMethod(Map<String, dynamic> op) {
-    _channel?.invokeMethod(op['method'], op['args']).catchError((e) {
-      debugPrint('Method ${op['method']} failed: $e');
-    });
-  }
-
   @override
   void dispose() {
     _isDisposed = true;
@@ -246,19 +270,72 @@ class MapControllerImpl implements MapController {
     }
   }
 
-  /// iOS 需要一个Map 而不是json string
-  dynamic _encodeJson(dynamic object) {
-    // iOS平台直接返回对象或转换为JSON对象
-    if (Platform.isIOS) {
-      if (object is List) {
-        return object
-            .map((item) => item is Codable ? item.toJson() : item)
-            .toList();
+  @override
+  void updateOrAddCircle(String key, CircleOptions circleOptions) {
+    if (_isDisposed) return;
+    final params = {
+      'method': MapConstants.methodUpdateOrAddCircle,
+      'args': {
+        'key': key,
+        'circleOptions': _encodeJson(circleOptions),
       }
-      return object is Codable ? object.toJson() : object;
+    };
+    debugPrint("updateOrAddCircle...params==>$params");
+    if (_channel != null) {
+      _executeMethod(params);
+    } else {
+      _pendingOperations.add(params);
     }
+  }
 
-    // 其他平台序列化为JSON字符串
-    return jsonEncode(object);
+  @override
+  void setOnCameraChangeListener(OnCameraChangeListener listener) {
+    if (_isDisposed) return;
+
+    setCameraHandler() {
+      const EventChannel changeChannel =
+          EventChannel(MapConstants.mapCameraChangeEventChannel);
+      changeChannel.receiveBroadcastStream().map((event) {
+        // 显式转换为 Map<String, dynamic>
+        return (event as Map).cast<String, dynamic>();
+      }).listen((data) {
+        //减少对象创建,把CameraPosition放到同一层了,返回数据如下 : {"function":"onCameraChange","longitude":116.397128,"latitude":39.916527,"zoom":10}
+        CameraChange cameraChange = CameraChange.fromJson(data);
+        listener.call(cameraChange);
+      });
+    }
+
+    final params = {
+      'method': MapConstants.methodSetCameraChangeEventChannel,
+      'args': '',
+      'invokeSuccessCallback': setCameraHandler
+    };
+
+    debugPrint("setOnCameraChangeListener...params==>$params");
+    if (_channel != null) {
+      _executeMethod(params);
+    } else {
+      _pendingOperations.add(params);
+    }
+  }
+
+  @override
+  void moveCameraToBounds(LatLngBounds bounds, {MapPadding? mapPadding}) {
+    if (_isDisposed) return;
+    Map<String, dynamic> map = {};
+    map['bounds'] = _encodeJson(bounds);
+    if (mapPadding != null) {
+      map['mapPadding'] = _encodeJson(mapPadding);
+    }
+    final params = {
+      'method': MapConstants.methodMoveCameraToBounds,
+      'args': map
+    };
+    debugPrint("moveCameraToBounds...params==>$params");
+    if (_channel != null) {
+      _executeMethod(params);
+    } else {
+      _pendingOperations.add(params);
+    }
   }
 }

+ 1 - 0
plugins/map/lib/src/core/map_platform.dart

@@ -3,6 +3,7 @@ import 'dart:convert';
 import 'package:flutter/services.dart';
 import 'package:flutter_map/src/interface/function/map_trace_interface.dart';
 import '../../flutter_map.dart';
+import '../interface/map_sdk_interface.dart';
 
 abstract class MapPlatform implements MapSDKInterface, MapTraceInterface {}
 

+ 20 - 0
plugins/map/lib/src/entity/camera_change.dart

@@ -0,0 +1,20 @@
+import '../../flutter_map.dart';
+
+typedef OnCameraChangeListener = void Function(CameraChange cameraChange);
+
+class CameraChange {
+  CameraChangeType function; //onCameraChange | onCameraChangeFinish
+
+  CameraPosition position;
+
+  CameraChange({required this.function, required this.position});
+
+  factory CameraChange.fromJson(Map<String, dynamic> map) {
+    return CameraChange(
+      function: map['function'] == CameraChangeType.onCameraChange.value
+          ? CameraChangeType.onCameraChange
+          : CameraChangeType.onCameraChangeFinish,
+      position: CameraPosition.fromJson(map),
+    );
+  }
+}

+ 1 - 1
plugins/map/lib/src/entity/camera_position.dart

@@ -5,7 +5,7 @@ class CameraPosition implements Codable {
 
   double? latitude;
 
-  int zoom;
+  double zoom;
 
   CameraPosition({
     required this.longitude,

+ 23 - 0
plugins/map/lib/src/entity/latlng_bounds.dart

@@ -0,0 +1,23 @@
+import '../../flutter_map.dart';
+
+class LatLngBounds {
+  LatLng southwest;
+
+  LatLng northeast;
+
+  LatLngBounds({required this.southwest, required this.northeast});
+
+  factory LatLngBounds.fromJson(Map<String, dynamic> map) {
+    return LatLngBounds(
+      southwest: LatLng.fromJson(map['southwest']),
+      northeast: LatLng.fromJson(map['northeast']),
+    );
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      'southwest': southwest.toJson(),
+      'northeast': northeast.toJson(),
+    };
+  }
+}

+ 8 - 0
plugins/map/lib/src/interface/handle/map_handle_interface.dart

@@ -1,10 +1,15 @@
 import '../../../flutter_map.dart';
+import '../../entity/camera_change.dart';
+import '../../entity/latlng_bounds.dart';
 
 /// 地图操作相关接口
 abstract class MapHandleInterface {
   //移动地图
   void moveCamera(CameraPosition cameraPosition);
 
+  //移动地图到指定界限
+  void moveCameraToBounds(LatLngBounds bounds, {MapPadding? mapPadding});
+
   //动态移动地图
   void animateCamera(CameraPosition cameraPosition);
 
@@ -19,4 +24,7 @@ abstract class MapHandleInterface {
 
   //显示或隐藏地图logo
   void setMapLogoVisible(bool visible);
+
+  //设置地图移动监听
+  void setOnCameraChangeListener(OnCameraChangeListener listener);
 }

+ 1 - 3
plugins/map/lib/src/interface/map_sdk_interface.dart

@@ -1,6 +1,4 @@
-import '../entity/map_location.dart';
-
-typedef MapLocationListener = void Function(MapLocation location);
+import '../../flutter_map.dart';
 
 /// 地图SDK相关接口
 abstract class MapSDKInterface {

+ 7 - 2
plugins/map/lib/src/interface/overlays/map_face_interface.dart

@@ -1,3 +1,8 @@
-//绘制面
+import '../../../flutter_map.dart';
+import '../../consts/circle_options.dart';
 
-abstract class MapFaceInterface {}
+//绘制面
+abstract class MapFaceInterface {
+  //更新或添加一个圆,key一样则更新
+  void updateOrAddCircle(String key, CircleOptions circleOptions);
+}

+ 32 - 0
plugins/map/lib/src/utils/camera_update_util.dart

@@ -0,0 +1,32 @@
+import 'dart:math' show cos, pi;
+
+import '../../flutter_map.dart';
+import '../entity/latlng_bounds.dart';
+
+class CameraUpdateUtil {
+  /// 根据圆心和半径,计算外接矩形范围
+  static LatLngBounds? getBounds(
+      double? latitude, double? longitude, double? radius) {
+    if (latitude == null || longitude == null || radius == null) {
+      return null;
+    }
+    // 半径转经纬度差值
+    final double latRadius = radius / 111000.0;
+    final double lngRadius = radius / (111000.0 * cos(latitude * pi / 180));
+
+    final LatLng southwest = LatLng(
+      latitude: latitude - latRadius,
+      longitude: longitude - lngRadius,
+    );
+
+    final LatLng northeast = LatLng(
+      latitude: latitude + latRadius,
+      longitude: longitude + lngRadius,
+    );
+
+    return LatLngBounds(
+      southwest: southwest,
+      northeast: northeast,
+    );
+  }
+}

+ 1 - 1
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/FlutterViewPlugin.java

@@ -15,6 +15,6 @@ public class FlutterViewPlugin {
 
 
     public static void registerWith(FlutterPlugin.FlutterPluginBinding flutterPluginBinding, Activity mActivity, LifecycleOwner lifecycleProvider) {
-        flutterPluginBinding.getPlatformViewRegistry().registerViewFactory(Constants.MAP_WIDGET_VIEW_TYPE, new MapViewFactory(mActivity, flutterPluginBinding.getBinaryMessenger(), lifecycleProvider));
+        flutterPluginBinding.getPlatformViewRegistry().registerViewFactory(Constants.MAP_WIDGET_VIEW_TYPE, new MapViewFactory(mActivity,flutterPluginBinding, lifecycleProvider));
     }
 }

+ 4 - 1
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/MapAmapAndroidPlugin.java

@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
 
 import com.atmob.map_amap_android.contants.Constants;
+import com.atmob.map_amap_android.event.FlutterLocationEventPlugin;
 import com.atmob.map_amap_android.lifecycle.FlutterLifecycleAdapter;
 import com.atmob.map_amap_android.trace.TraceClientHelper;
 import com.atmob.map_amap_android.util.AMapHelper;
@@ -37,6 +38,7 @@ public class MapAmapAndroidPlugin implements FlutterPlugin, MethodCallHandler, A
     private Context applicationContext;
     private FlutterPluginBinding flutterPluginBinding;
     private Lifecycle lifecycle;
+    FlutterLocationEventPlugin flutterLocationEventPlugin;
 
     @Override
     public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
@@ -45,7 +47,7 @@ public class MapAmapAndroidPlugin implements FlutterPlugin, MethodCallHandler, A
         applicationContext = flutterPluginBinding.getApplicationContext();
         channel.setMethodCallHandler(this);
         this.flutterPluginBinding = flutterPluginBinding;
-        FlutterLocationEventPlugin flutterLocationEventPlugin = new FlutterLocationEventPlugin();
+        flutterLocationEventPlugin = new FlutterLocationEventPlugin();
         flutterLocationEventPlugin.onAttachedToEngine(flutterPluginBinding);
     }
 
@@ -76,6 +78,7 @@ public class MapAmapAndroidPlugin implements FlutterPlugin, MethodCallHandler, A
     @Override
     public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
         channel.setMethodCallHandler(null);
+        flutterLocationEventPlugin.onDetachedFromEngine(binding);
     }
 
     @Override

+ 24 - 9
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/amap/AmapView.java

@@ -12,10 +12,10 @@ import androidx.lifecycle.LifecycleOwner;
 
 import com.amap.api.maps.AMap;
 import com.amap.api.maps.CameraUpdateFactory;
-import com.amap.api.maps.MapView;
 import com.amap.api.maps.TextureMapView;
 import com.atmob.map_amap_android.contants.Constants;
 import com.atmob.map_amap_android.overlays.MyMethodCallHandler;
+import com.atmob.map_amap_android.overlays.face.FaceController;
 import com.atmob.map_amap_android.overlays.marker.MarkersController;
 import com.atmob.map_amap_android.overlays.polyline.PolylineController;
 import com.atmob.map_amap_android.util.LogUtil;
@@ -24,8 +24,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
-import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.platform.PlatformView;
@@ -34,6 +34,8 @@ public class AmapView implements PlatformView, DefaultLifecycleObserver, MethodC
 
     private final String TAG = "AmapView";
 
+    private final FlutterPlugin.FlutterPluginBinding flutterPluginBinding;
+
     private static final int DEFAULT_MAP_ZOOM_SCALE = 18;
 
     private final MethodChannel channel;
@@ -50,13 +52,16 @@ public class AmapView implements PlatformView, DefaultLifecycleObserver, MethodC
 
     private PolylineController polylineController;
 
+    private FaceController faceController;
+
     private final int viewId;
 
     private Bundle bundle;
 
-    public AmapView(Context context, Activity activity, BinaryMessenger messenger, int viewId, Map<String, Object> args, LifecycleOwner lifecycleProvider) {
+    public AmapView(Context context, Activity activity, FlutterPlugin.FlutterPluginBinding flutterPluginBinding, int viewId, Map<String, Object> args, LifecycleOwner lifecycleProvider) {
         this.viewId = viewId;
-        channel = new MethodChannel(messenger, Constants.MAP_VIEW_CHANNEL_NAME + viewId);
+        this.flutterPluginBinding = flutterPluginBinding;
+        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), Constants.MAP_VIEW_CHANNEL_NAME + viewId);
         channel.setMethodCallHandler(this);
         myMethodCallHandlerMap = new HashMap<>(8);
 
@@ -79,9 +84,10 @@ public class AmapView implements PlatformView, DefaultLifecycleObserver, MethodC
             });
             AMap amap = mapView.getMap();
             initMapDefaultSetting(amap);
-            mapController = new MapController(context, channel, amap);
+            mapController = new MapController(context, channel, flutterPluginBinding, amap);
             markersController = new MarkersController(context, channel, amap);
             polylineController = new PolylineController(context, channel, amap);
+            faceController = new FaceController(context, channel, amap);
 
             initMyMethodCallHandlerMap();
 
@@ -112,6 +118,13 @@ public class AmapView implements PlatformView, DefaultLifecycleObserver, MethodC
                 myMethodCallHandlerMap.put(methodId, polylineController);
             }
         }
+
+        methodIdArray = faceController.getRegisterMethodIdArray();
+        if (null != methodIdArray) {
+            for (String methodId : methodIdArray) {
+                myMethodCallHandlerMap.put(methodId, faceController);
+            }
+        }
     }
 
     private void initMapDefaultSetting(AMap map) {
@@ -249,9 +262,11 @@ public class AmapView implements PlatformView, DefaultLifecycleObserver, MethodC
     }
 
     private void destroyMapViewIfNecessary() {
-        if (mapView == null) {
-            return;
-        }
-        mapView.onDestroy();
+        if (mapView != null) mapView.onDestroy();
+        if (mapController != null) mapController.dispose();
+        if (markersController != null) markersController.dispose();
+        if (polylineController != null) polylineController.dispose();
+        if (faceController != null) faceController.dispose();
+
     }
 }

+ 94 - 2
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/amap/MapController.java

@@ -5,13 +5,14 @@ import android.content.Context;
 import androidx.annotation.NonNull;
 
 import com.amap.api.maps.AMap;
-import com.amap.api.maps.AMapOptions;
 import com.amap.api.maps.CameraUpdate;
 import com.amap.api.maps.CameraUpdateFactory;
+import com.amap.api.maps.model.CameraPosition;
 import com.amap.api.maps.model.LatLng;
 import com.amap.api.maps.model.LatLngBounds;
 import com.atmob.map_amap_android.bean.MapPadding;
 import com.atmob.map_amap_android.contants.Constants;
+import com.atmob.map_amap_android.event.FlutterCameraChangeEventPlugin;
 import com.atmob.map_amap_android.overlays.MyMethodCallHandler;
 import com.atmob.map_amap_android.util.GsonUtil;
 import com.atmob.map_amap_android.util.LogUtil;
@@ -23,6 +24,7 @@ import com.google.gson.reflect.TypeToken;
 import java.util.List;
 import java.util.Map;
 
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 
@@ -35,11 +37,14 @@ public class MapController implements MyMethodCallHandler {
     private final Context context;
 
     private final MethodChannel methodChannel;
+    private final FlutterPlugin.FlutterPluginBinding flutterPluginBinding;
 
     int nowMapTypeIndex = 0;
 
     private final Gson gson;
 
+    FlutterCameraChangeEventPlugin cameraChangeEventPlugin;
+
     private final int[] mapType = {
             AMap.MAP_TYPE_NORMAL,
             AMap.MAP_TYPE_SATELLITE,
@@ -49,9 +54,10 @@ public class MapController implements MyMethodCallHandler {
             AMap.MAP_TYPE_NAVI_NIGHT,
     };
 
-    public MapController(Context context, MethodChannel methodChannel, AMap map) {
+    public MapController(Context context, MethodChannel methodChannel, FlutterPlugin.FlutterPluginBinding flutterPluginBinding, AMap map) {
         this.context = context;
         this.methodChannel = methodChannel;
+        this.flutterPluginBinding = flutterPluginBinding;
         this.map = map;
         map.setMapType(mapType[nowMapTypeIndex]);
         gson = GsonUtil.getInstance();
@@ -64,6 +70,9 @@ public class MapController implements MyMethodCallHandler {
             case Constants.METHOD_MOVE_CAMERA:
                 moveToCamera(call, result);
                 break;
+            case Constants.METHOD_MOVE_CAMERA_TO_BOUNDS:
+                moveCameraToBounds(call, result);
+                break;
             case Constants.METHOD_ANIMATE_CAMERA:
                 animateCamera(call, result);
                 break;
@@ -79,8 +88,82 @@ public class MapController implements MyMethodCallHandler {
             case Constants.METHOD_MAP_LOGO_VISIBLE:
                 mapLogoVisible(call, result);
                 break;
+            case Constants.METHOD_SET_CAMERA_CHANGE_EVENT_CHANNEL:
+                setCameraChangeEventChannel(call, result);
+                break;
+        }
+    }
+
+    private void moveCameraToBounds(MethodCall call, MethodChannel.Result result) {
+        LogUtil.i(TAG, "moveCameraToBounds===>" + call.arguments());
+        if (map == null) {
+            result.error("map is null", null, null);
+            return;
+        }
+        try {
+            Map<String, Object> arguments = call.arguments();
+            String boundsStr = ParamUtil.getString(arguments, "bounds");
+            if (boundsStr == null || boundsStr.isEmpty()) {
+                result.error("bounds is null", null, null);
+                return;
+            }
+            LatLngBounds bounds = gson.fromJson(boundsStr, LatLngBounds.class);
+            if (bounds == null) {
+                result.error("bounds parse error", null, null);
+                return;
+            }
+            String paddingStr = ParamUtil.getString(arguments, "mapPadding");
+
+            MapPadding padding = null;
+            if (paddingStr != null && !paddingStr.isEmpty()) {
+                padding = gson.fromJson(paddingStr, MapPadding.class);
+            }
+            int left = 0;
+            int top = 0;
+            int right = 0;
+            int bottom = 0;
+            if (padding != null) {
+                left = (int) SizeUtil.dp2px(context, padding.getLeft());
+                top = (int) SizeUtil.dp2px(context, padding.getTop());
+                right = (int) SizeUtil.dp2px(context, padding.getRight());
+                bottom = (int) SizeUtil.dp2px(context, padding.getBottom());
+            }
+            CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBoundsRect(bounds, left, right, top, bottom);
+            map.moveCamera(cameraUpdate);
+            result.success(null);
+        } catch (Exception e) {
+            LogUtil.d(TAG, "moveCameraToBounds error===>" + e.getMessage());
+            result.error("JsonSyntaxException", e.getMessage(), null);
+        }
+    }
 
+    private void setCameraChangeEventChannel(MethodCall call, MethodChannel.Result result) {
+        if (map == null) {
+            result.error("map is null", null, null);
+            return;
         }
+        if (cameraChangeEventPlugin != null) {
+            return;
+        }
+        cameraChangeEventPlugin = new FlutterCameraChangeEventPlugin();
+        map.setOnCameraChangeListener(new AMap.OnCameraChangeListener() {
+            @Override
+            public void onCameraChange(CameraPosition cameraPosition) {
+//                LogUtil.d(TAG, "onCameraChange===>" + cameraPosition);
+                // 地图移动过程中持续回调
+                cameraChangeEventPlugin.senCameraChange("onCameraChange", cameraPosition);
+            }
+
+            @Override
+            public void onCameraChangeFinish(CameraPosition cameraPosition) {
+//                LogUtil.d(TAG, "onCameraChangeFinish===>" + cameraPosition);
+                // 地图移动结束时回调
+                cameraChangeEventPlugin.senCameraChange("onCameraChangeFinish", cameraPosition);
+            }
+        });
+        cameraChangeEventPlugin.onAttachedToEngine(flutterPluginBinding);
+        LogUtil.d(TAG, "setCameraChangeEventChannel success");
+        result.success(null);
     }
 
     private void mapInteractionEnabled(MethodCall call, MethodChannel.Result result) {
@@ -212,4 +295,13 @@ public class MapController implements MyMethodCallHandler {
     public String[] getRegisterMethodIdArray() {
         return Constants.METHOD_ID_LIST_FOR_MAP;
     }
+
+    @Override
+    public void dispose() {
+        if (cameraChangeEventPlugin != null) {
+            if (map != null) map.setOnCameraChangeListener(null);
+            cameraChangeEventPlugin.onDetachedFromEngine(flutterPluginBinding);
+            cameraChangeEventPlugin = null;
+        }
+    }
 }

+ 5 - 4
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/amap/MapViewFactory.java

@@ -9,6 +9,7 @@ import androidx.lifecycle.LifecycleOwner;
 
 import java.util.Map;
 
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.StandardMessageCodec;
 import io.flutter.plugin.platform.PlatformView;
@@ -17,13 +18,13 @@ import io.flutter.plugin.platform.PlatformViewFactory;
 public class MapViewFactory extends PlatformViewFactory {
 
     private final Activity activity;
-    private final BinaryMessenger messenger;
+    private final FlutterPlugin.FlutterPluginBinding flutterPluginBinding;
     private final LifecycleOwner lifecycleProvider;
 
-    public MapViewFactory(Activity activity, BinaryMessenger messenger, LifecycleOwner lifecycleProvider) {
+    public MapViewFactory(Activity activity, FlutterPlugin.FlutterPluginBinding flutterPluginBinding, LifecycleOwner lifecycleProvider) {
         super(StandardMessageCodec.INSTANCE);
         this.activity = activity;
-        this.messenger = messenger;
+        this.flutterPluginBinding = flutterPluginBinding;
         this.lifecycleProvider = lifecycleProvider;
     }
 
@@ -34,6 +35,6 @@ public class MapViewFactory extends PlatformViewFactory {
         if (args instanceof Map) {
             params = (Map<String, Object>) args;
         }
-        return new AmapView(context, activity, messenger, viewId, params,lifecycleProvider);
+        return new AmapView(context, activity, flutterPluginBinding, viewId, params,lifecycleProvider);
     }
 }

+ 17 - 2
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/contants/Constants.java

@@ -13,6 +13,9 @@ public class Constants {
     /*******渠道名称********/
     public static final String MAP_VIEW_CHANNEL_NAME = "com.atmob.flutter_map/map_view_";
 
+    // 地图移动监听持续流通道名称 可多个
+    public static final String MAP_CAMERA_CHANGE_EVENT_CHANNEL = "com.atmob.flutter_map/camera_change_event";
+
 
     //地图widget_viewType
     public static final String MAP_WIDGET_VIEW_TYPE = "com.atmob.flutter.map";
@@ -37,8 +40,12 @@ public class Constants {
     public static final String METHOD_MAP_MOVE_TO_SUITABLE_LOCATION = "map#moveToSuitableLocation";
     public static final String METHOD_MAP_INTERACTION_ENABLED = "map#interactionEnabled";
     public static final String METHOD_MAP_LOGO_VISIBLE = "map#logoVisible";
+    public static final String METHOD_SET_CAMERA_CHANGE_EVENT_CHANNEL = "map#setCameraChangeEventChannel";
+    public static final String METHOD_MOVE_CAMERA_TO_BOUNDS = "map#moveCameraToBounds";
 
-    public static final String[] METHOD_ID_LIST_FOR_MAP = {METHOD_MOVE_CAMERA, METHOD_ANIMATE_CAMERA, METHOD_MAP_CLEAR, METHOD_MAP_MOVE_TO_SUITABLE_LOCATION, METHOD_MAP_INTERACTION_ENABLED, METHOD_MAP_LOGO_VISIBLE};
+    public static final String[] METHOD_ID_LIST_FOR_MAP = {METHOD_MOVE_CAMERA, METHOD_ANIMATE_CAMERA, METHOD_MAP_CLEAR,
+            METHOD_MAP_MOVE_TO_SUITABLE_LOCATION, METHOD_MAP_INTERACTION_ENABLED, METHOD_MAP_LOGO_VISIBLE, METHOD_SET_CAMERA_CHANGE_EVENT_CHANNEL,
+            METHOD_MOVE_CAMERA_TO_BOUNDS};
 
     /**
      * markers
@@ -59,7 +66,15 @@ public class Constants {
     public static final String METHOD_REMOVE_POLYLINE = "polyline#removePolyline";
     public static final String METHOD_CLEAR_ALL_POLYLINES = "polyline#clearAllPolylines";
 
-    public static final String[] METHOD_ID_LIST_FOR_POLYLINE = {METHOD_UPDATE_OR_ADD_POLYLINE, METHOD_REMOVE_POLYLINE,METHOD_CLEAR_ALL_POLYLINES};
+    public static final String[] METHOD_ID_LIST_FOR_POLYLINE = {METHOD_UPDATE_OR_ADD_POLYLINE, METHOD_REMOVE_POLYLINE, METHOD_CLEAR_ALL_POLYLINES};
+
+
+    /**
+     * 面
+     */
+    public static final String METHOD_UPDATE_OR_ADD_CIRCLE = "face#updateOrAddCircle";
+
+    public static final String[] METHOD_ID_LIST_FOR_FACE = {METHOD_UPDATE_OR_ADD_CIRCLE};
 
 
 }

+ 85 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/event/FlutterCameraChangeEventPlugin.java

@@ -0,0 +1,85 @@
+package com.atmob.map_amap_android.event;
+
+import androidx.annotation.NonNull;
+
+import com.amap.api.maps.model.CameraPosition;
+import com.amap.api.maps.model.LatLng;
+import com.atmob.map_amap_android.contants.Constants;
+import com.atmob.map_amap_android.util.LogUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.EventChannel;
+
+public class FlutterCameraChangeEventPlugin implements FlutterPlugin, EventChannel.StreamHandler {
+
+    private static final String TAG = "FlutterCameraChangeEventPlugin";
+
+    private EventChannel eventChannel;
+    private EventChannel.EventSink eventSink;
+    private Map<String, Object> locationMap;
+
+    @Override
+    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
+        eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), Constants.MAP_CAMERA_CHANGE_EVENT_CHANNEL);
+        eventChannel.setStreamHandler(this);
+    }
+
+    @Override
+    public void onDetachedFromEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
+        if (eventChannel != null) {
+            eventChannel.setStreamHandler(null);
+        }
+        eventChannel = null;
+        eventSink = null;
+        if (locationMap != null) locationMap.clear();
+        locationMap = null;
+    }
+
+    @Override
+    public void onListen(Object o, EventChannel.EventSink eventSink) {
+        this.eventSink = eventSink;
+    }
+
+    @Override
+    public void onCancel(Object o) {
+        eventSink = null;
+        if (locationMap != null) locationMap.clear();
+        locationMap = null;
+    }
+
+
+    public void senCameraChange(String function, CameraPosition cameraPosition) {
+        try {
+            if (eventSink != null) {
+                Map<String, Object> data = obtainData(function, cameraPosition);
+                eventSink.success(data);
+                LogUtil.d(TAG, "senCameraChange success: " + data);
+            }
+        } catch (Exception e) {
+            LogUtil.d(TAG, "senCameraChange error: " + e.getMessage());
+            sendError("-1", e.getMessage());
+        }
+    }
+
+    private void sendError(String errorCode, String errorMessage) {
+        if (eventSink != null) {
+            eventSink.error(errorCode, errorMessage, null);
+        }
+    }
+
+    private Map<String, Object> obtainData(String function, CameraPosition cameraPosition) {
+        if (locationMap == null) {
+            locationMap = new HashMap<>(4);
+        }
+        locationMap.clear();
+        LatLng target = cameraPosition.target;
+        locationMap.put("function", function);
+        locationMap.put("longitude", target.longitude);
+        locationMap.put("latitude", target.latitude);
+        locationMap.put("zoom", cameraPosition.zoom);
+        return locationMap;
+    }
+}

+ 1 - 1
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/FlutterLocationEventPlugin.java

@@ -1,4 +1,4 @@
-package com.atmob.map_amap_android;
+package com.atmob.map_amap_android.event;
 
 import androidx.annotation.NonNull;
 

+ 2 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/MyMethodCallHandler.java

@@ -21,4 +21,6 @@ public interface MyMethodCallHandler {
      * @return
      */
     String[] getRegisterMethodIdArray();
+
+    void dispose();
 }

+ 122 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/face/FaceController.java

@@ -0,0 +1,122 @@
+package com.atmob.map_amap_android.overlays.face;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import com.amap.api.maps.AMap;
+import com.amap.api.maps.CameraUpdateFactory;
+import com.amap.api.maps.model.BaseOverlay;
+import com.amap.api.maps.model.Circle;
+import com.amap.api.maps.model.CircleOptions;
+import com.amap.api.maps.model.LatLng;
+import com.atmob.map_amap_android.contants.Constants;
+import com.atmob.map_amap_android.overlays.MyMethodCallHandler;
+import com.atmob.map_amap_android.util.GsonUtil;
+import com.atmob.map_amap_android.util.LogUtil;
+import com.atmob.map_amap_android.util.ParamUtil;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.util.HashMap;
+import java.util.Map;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+
+public class FaceController implements MyMethodCallHandler {
+
+
+    private final String TAG = "FaceController";
+
+    private final AMap map;
+
+    private final Context context;
+
+    private final HashMap<String, BaseOverlay> currentFaceMap = new HashMap<>(10);
+
+    private final MethodChannel methodChannel;
+    private final Gson gson;
+
+    public FaceController(Context context, MethodChannel methodChannel, AMap map) {
+        this.context = context;
+        this.methodChannel = methodChannel;
+        this.map = map;
+        gson = GsonUtil.getInstance();
+    }
+
+    @Override
+    public void doMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
+        LogUtil.i(TAG, "doMethodCall===>" + call.method + "  arguments===>" + call.arguments);
+        switch (call.method) {
+            case Constants.METHOD_UPDATE_OR_ADD_CIRCLE:
+                updateOrAddCircle(call, result);
+                break;
+        }
+    }
+
+    private void updateOrAddCircle(MethodCall call, MethodChannel.Result result) {
+        try {
+            Map<String, Object> arguments = call.arguments();
+            if (arguments == null) {
+                result.error("arguments is null", null, null);
+                return;
+            }
+            String faceId = ParamUtil.getString(arguments, "key");
+            String options = ParamUtil.getString(arguments, "circleOptions");
+            if (TextUtils.isEmpty(faceId) || TextUtils.isEmpty(options)) {
+                result.error("faceId or circleOptions is null", null, null);
+                return;
+            }
+            FlutterCircleOptions flutterCircleOptions = gson.fromJson(options, new TypeToken<FlutterCircleOptions>() {
+            }.getType());
+            if (flutterCircleOptions == null) {
+                result.error("circleOptions parse error", null, null);
+                return;
+            }
+            updateOrAddCircle(faceId, flutterCircleOptions);
+            result.success(null);
+        } catch (Exception e) {
+            LogUtil.d(TAG, "updateOrAddCircle error===>" + e.getMessage());
+            result.error("JsonSyntaxException", e.getMessage(), null);
+        }
+    }
+
+    @Override
+    public String[] getRegisterMethodIdArray() {
+        return Constants.METHOD_ID_LIST_FOR_FACE;
+    }
+
+    @Override
+    public void dispose() {
+
+    }
+
+
+    private void updateOrAddCircle(String faceId, FlutterCircleOptions options) {
+        if (map == null) {
+            throw new IllegalArgumentException("map is null");
+        }
+        BaseOverlay overlay = currentFaceMap.get(faceId);
+        Circle targetCircle = null;
+        if (overlay instanceof Circle) {
+            targetCircle = (Circle) overlay;
+        }
+        if (targetCircle == null) {
+            Circle circle = map.addCircle(new CircleOptions()
+                    .center(new LatLng(options.getLatitude(), options.getLongitude()))
+                    .radius(options.getRadius())
+                    .visible(options.isVisible())
+                    .fillColor(Color.parseColor(options.getFillColor()))
+                    .strokeColor(Color.parseColor(options.getStrokeColor()))
+                    .strokeWidth(options.getStrokeWidth()));
+            currentFaceMap.put(faceId, circle);
+        } else {
+            targetCircle.setCenter(new LatLng(options.getLatitude(), options.getLongitude()));
+            targetCircle.setRadius(options.getRadius());
+            targetCircle.setVisible(options.isVisible());
+            targetCircle.setFillColor(Color.parseColor(options.getFillColor()));
+            targetCircle.setStrokeColor(Color.parseColor(options.getStrokeColor()));
+            targetCircle.setStrokeWidth(options.getStrokeWidth());
+        }
+    }
+
+}

+ 88 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/face/FlutterCircleOptions.java

@@ -0,0 +1,88 @@
+package com.atmob.map_amap_android.overlays.face;
+
+import com.google.gson.annotations.SerializedName;
+
+public class FlutterCircleOptions {
+
+    @SerializedName("latitude")
+    private double latitude;
+
+    @SerializedName("longitude")
+    private double longitude;
+
+    @SerializedName("radius")
+    private double radius;
+
+    @SerializedName("strokeColor")
+    private String strokeColor;
+
+    @SerializedName("fillColor")
+    private String fillColor;
+
+    @SerializedName("strokeWidth")
+    private float strokeWidth;
+
+    @SerializedName("visible")
+    private boolean visible;
+
+    public void setLatitude(double latitude) {
+        this.latitude = latitude;
+    }
+
+    public void setLongitude(double longitude) {
+        this.longitude = longitude;
+    }
+
+    public void setStrokeWidth(float strokeWidth) {
+        this.strokeWidth = strokeWidth;
+    }
+
+    public boolean isVisible() {
+        return visible;
+    }
+
+    public void setVisible(boolean visible) {
+        this.visible = visible;
+    }
+
+    public void setFillColor(String fillColor) {
+        this.fillColor = fillColor;
+    }
+
+    public void setRadius(double radius) {
+        this.radius = radius;
+    }
+
+    public void setStrokeColor(String strokeColor) {
+        this.strokeColor = strokeColor;
+    }
+
+    public void setStrokeWidth(int strokeWidth) {
+        this.strokeWidth = strokeWidth;
+    }
+
+
+    public String getFillColor() {
+        return fillColor;
+    }
+
+    public double getRadius() {
+        return radius;
+    }
+
+    public String getStrokeColor() {
+        return strokeColor;
+    }
+
+    public float getStrokeWidth() {
+        return strokeWidth;
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+}

+ 5 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/marker/MarkersController.java

@@ -285,6 +285,11 @@ public class MarkersController implements MyMethodCallHandler, AMap.OnMarkerClic
         return Constants.METHOD_ID_LIST_FOR_MARKER;
     }
 
+    @Override
+    public void dispose() {
+
+    }
+
 
     @SuppressLint("SetTextI18n")
     public BitmapDescriptor getMarkerBitmap(MakerInfo markerInfo, Bitmap customAvatarBitmap) {

+ 5 - 0
plugins/map_amap_android/android/src/main/java/com/atmob/map_amap_android/overlays/polyline/PolylineController.java

@@ -219,4 +219,9 @@ public class PolylineController implements MyMethodCallHandler {
     public String[] getRegisterMethodIdArray() {
         return Constants.METHOD_ID_LIST_FOR_POLYLINE;
     }
+
+    @Override
+    public void dispose() {
+
+    }
 }