25 changed files with 2608 additions and 5 deletions
@ -0,0 +1,174 @@ |
|||
part of amap_service_map; |
|||
|
|||
final MethodChannelAMapFlutterMap _methodChannel = |
|||
AMapFlutterPlatform.instance as MethodChannelAMapFlutterMap; |
|||
|
|||
/// 地图通信中心 |
|||
class AMapController { |
|||
final int mapId; |
|||
final _MapState _mapState; |
|||
|
|||
AMapController._( |
|||
CameraPosition initCameraPosition, |
|||
this._mapState, { |
|||
required this.mapId, |
|||
}) { |
|||
_connectStreams(mapId); |
|||
} |
|||
|
|||
///根据传入的id初始化[AMapController] |
|||
/// 主要用于在[AMapWidget]初始化时在[AMapWidget.onMapCreated]中初始化controller |
|||
static Future<AMapController> init( |
|||
int id, |
|||
CameraPosition initialCameration, |
|||
_MapState mapState, |
|||
) async { |
|||
await _methodChannel.init(id); |
|||
return AMapController._(initialCameration, mapState, mapId: id); |
|||
} |
|||
|
|||
///只用于测试 |
|||
///用于与native的通信 |
|||
@visibleForTesting |
|||
MethodChannel get channel { |
|||
return _methodChannel.channel(mapId); |
|||
} |
|||
|
|||
void _connectStreams(int mapId) { |
|||
if (_mapState.widget.onLocationChanged != null) { |
|||
_methodChannel |
|||
.onLocationChanged(mapId: mapId) |
|||
.listen( |
|||
(LocationChangedEvent e) => |
|||
_mapState.widget.onLocationChanged!(e.value), |
|||
); |
|||
} |
|||
|
|||
if (_mapState.widget.onCameraMove != null) { |
|||
_methodChannel |
|||
.onCameraMove(mapId: mapId) |
|||
.listen( |
|||
(CameraPositionMoveEvent e) => |
|||
_mapState.widget.onCameraMove!(e.value), |
|||
); |
|||
} |
|||
if (_mapState.widget.onCameraMoveEnd != null) { |
|||
_methodChannel |
|||
.onCameraMoveEnd(mapId: mapId) |
|||
.listen( |
|||
(CameraPositionMoveEndEvent e) => |
|||
_mapState.widget.onCameraMoveEnd!(e.value), |
|||
); |
|||
} |
|||
if (_mapState.widget.onTap != null) { |
|||
_methodChannel |
|||
.onMapTap(mapId: mapId) |
|||
.listen(((MapTapEvent e) => _mapState.widget.onTap!(e.value))); |
|||
} |
|||
if (_mapState.widget.onLongPress != null) { |
|||
_methodChannel |
|||
.onMapLongPress(mapId: mapId) |
|||
.listen( |
|||
((MapLongPressEvent e) => _mapState.widget.onLongPress!(e.value)), |
|||
); |
|||
} |
|||
|
|||
if (_mapState.widget.onPoiTouched != null) { |
|||
_methodChannel |
|||
.onPoiTouched(mapId: mapId) |
|||
.listen( |
|||
((MapPoiTouchEvent e) => _mapState.widget.onPoiTouched!(e.value)), |
|||
); |
|||
} |
|||
|
|||
_methodChannel |
|||
.onMarkerTap(mapId: mapId) |
|||
.listen((MarkerTapEvent e) => _mapState.onMarkerTap(e.value)); |
|||
|
|||
_methodChannel |
|||
.onMarkerDragEnd(mapId: mapId) |
|||
.listen( |
|||
(MarkerDragEndEvent e) => |
|||
_mapState.onMarkerDragEnd(e.value, e.position), |
|||
); |
|||
|
|||
_methodChannel |
|||
.onPolylineTap(mapId: mapId) |
|||
.listen((PolylineTapEvent e) => _mapState.onPolylineTap(e.value)); |
|||
} |
|||
|
|||
void disponse() { |
|||
_methodChannel.dispose(id: mapId); |
|||
} |
|||
|
|||
Future<void> _updateMapOptions(Map<String, dynamic> optionsUpdate) { |
|||
return _methodChannel.updateMapOptions(optionsUpdate, mapId: mapId); |
|||
} |
|||
|
|||
Future<void> _updateMarkers(MarkerUpdates markerUpdates) { |
|||
return _methodChannel.updateMarkers(markerUpdates, mapId: mapId); |
|||
} |
|||
|
|||
Future<void> _updatePolylines(PolylineUpdates polylineUpdates) { |
|||
return _methodChannel.updatePolylines(polylineUpdates, mapId: mapId); |
|||
} |
|||
|
|||
Future<void> _updatePolygons(PolygonUpdates polygonUpdates) { |
|||
return _methodChannel.updatePolygons(polygonUpdates, mapId: mapId); |
|||
} |
|||
|
|||
///改变地图视角 |
|||
/// |
|||
///通过[CameraUpdate]对象设置新的中心点、缩放比例、放大缩小、显示区域等内容 |
|||
/// |
|||
///(注意:iOS端设置显示区域时,不支持duration参数,动画时长使用iOS地图默认值350毫秒) |
|||
/// |
|||
///可选属性[animated]用于控制是否执行动画移动 |
|||
/// |
|||
///可选属性[duration]用于控制执行动画的时长,默认250毫秒,单位:毫秒 |
|||
Future<void> moveCamera( |
|||
CameraUpdate cameraUpdate, { |
|||
bool animated = true, |
|||
int duration = 250, |
|||
}) { |
|||
return _methodChannel.moveCamera( |
|||
cameraUpdate, |
|||
mapId: mapId, |
|||
animated: animated, |
|||
duration: duration, |
|||
); |
|||
} |
|||
|
|||
///设置地图每秒渲染的帧数 |
|||
Future<void> setRenderFps(int fps) { |
|||
return _methodChannel.setRenderFps(fps, mapId: mapId); |
|||
} |
|||
|
|||
///地图截屏 |
|||
Future<Uint8List?> takeSnapshot() { |
|||
return _methodChannel.takeSnapshot(mapId: mapId); |
|||
} |
|||
|
|||
/// 获取地图审图号(普通地图) |
|||
/// |
|||
/// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 |
|||
/// |
|||
/// 如高德地图在"关于"中体现 |
|||
Future<String?> getMapContentApprovalNumber() { |
|||
return _methodChannel.getMapContentApprovalNumber(mapId: mapId); |
|||
} |
|||
|
|||
/// 获取地图审图号(卫星地图) |
|||
/// |
|||
/// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 |
|||
/// |
|||
/// 如高德地图在"关于"中体现 |
|||
Future<String?> getSatelliteImageApprovalNumber() { |
|||
return _methodChannel.getSatelliteImageApprovalNumber(mapId: mapId); |
|||
} |
|||
|
|||
/// 清空缓存 |
|||
Future<void> clearDisk() { |
|||
return _methodChannel.clearDisk(mapId: mapId); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
/// 高德地图Flutter插件入口文件 |
|||
library amap_service_map; |
|||
|
|||
import 'dart:async'; |
|||
import 'dart:typed_data'; |
|||
|
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; |
|||
import 'package:amap_flutter_map/src/core/map_event.dart'; |
|||
import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; |
|||
import 'package:amap_flutter_map/src/types/types.dart'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:flutter/gestures.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
|
|||
export 'package:amap_flutter_map/src/types/types.dart'; |
|||
|
|||
part 'amap_controller.dart'; |
|||
part 'amap_widget.dart'; |
|||
@ -0,0 +1,425 @@ |
|||
part of amap_service_map; |
|||
|
|||
typedef void MapCreatedCallback(AMapController controller); |
|||
|
|||
///用于展示高德地图的Widget |
|||
class AMapWidget extends StatefulWidget { |
|||
///高德开放平台的key |
|||
/// |
|||
final AMapApiKey? apiKey; |
|||
|
|||
/// 初始化时的地图中心点 |
|||
final CameraPosition initialCameraPosition; |
|||
|
|||
///地图类型 |
|||
final MapType mapType; |
|||
|
|||
///自定义地图样式 |
|||
final CustomStyleOptions? customStyleOptions; |
|||
|
|||
///定位小蓝点 |
|||
final MyLocationStyleOptions? myLocationStyleOptions; |
|||
|
|||
///缩放级别范围 |
|||
final MinMaxZoomPreference? minMaxZoomPreference; |
|||
|
|||
///地图显示范围 |
|||
final LatLngBounds? limitBounds; |
|||
|
|||
///显示路况开关 |
|||
final bool trafficEnabled; |
|||
|
|||
/// 地图poi是否允许点击 |
|||
final bool touchPoiEnabled; |
|||
|
|||
///是否显示3D建筑物 |
|||
final bool buildingsEnabled; |
|||
|
|||
///是否显示底图文字标注 |
|||
final bool labelsEnabled; |
|||
|
|||
///是否显示指南针 |
|||
final bool compassEnabled; |
|||
|
|||
///是否显示比例尺 |
|||
final bool scaleEnabled; |
|||
|
|||
///是否支持缩放手势 |
|||
final bool zoomGesturesEnabled; |
|||
|
|||
///是否支持滑动手势 |
|||
final bool scrollGesturesEnabled; |
|||
|
|||
///是否支持旋转手势 |
|||
final bool rotateGesturesEnabled; |
|||
|
|||
///是否支持倾斜手势 |
|||
final bool tiltGesturesEnabled; |
|||
|
|||
/// 地图上显示的Marker |
|||
final Set<Marker> markers; |
|||
|
|||
/// 地图上显示的polyline |
|||
final Set<Polyline> polylines; |
|||
|
|||
/// 地图上显示的polygon |
|||
final Set<Polygon> polygons; |
|||
|
|||
/// 地图创建成功的回调, 收到此回调之后才可以操作地图 |
|||
final MapCreatedCallback? onMapCreated; |
|||
|
|||
/// 相机视角持续移动的回调 |
|||
final ArgumentCallback<CameraPosition>? onCameraMove; |
|||
|
|||
/// 相机视角移动结束的回调 |
|||
final ArgumentCallback<CameraPosition>? onCameraMoveEnd; |
|||
|
|||
/// 地图单击事件的回调 |
|||
final ArgumentCallback<LatLng>? onTap; |
|||
|
|||
/// 地图长按事件的回调 |
|||
final ArgumentCallback<LatLng>? onLongPress; |
|||
|
|||
/// 地图POI的点击回调,需要`touchPoiEnabled`true,才能回调 |
|||
final ArgumentCallback<AMapPoi>? onPoiTouched; |
|||
|
|||
///位置回调 |
|||
final ArgumentCallback<AMapLocation>? onLocationChanged; |
|||
|
|||
///需要应用到地图上的手势集合 |
|||
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers; |
|||
|
|||
///高德合规声明配置 |
|||
/// |
|||
/// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy |
|||
final AMapPrivacyStatement? privacyStatement; |
|||
|
|||
/// 创建一个展示高德地图的widget |
|||
/// |
|||
/// 如果使用的高德地图SDK的版本是8.1.0及以上版本, |
|||
/// 在app首次启动时必须传入高德合规声明配置[privacyStatement],后续如果没有变化不需要重复设置 |
|||
/// <li>[privacyStatement.hasContains] 隐私权政策是否包含高德开平隐私权政策</li> |
|||
/// <li>[privacyStatement.hasShow] 是否已经弹窗展示给用户</li> |
|||
/// <li>[privacyStatement.hasAgree] 隐私权政策是否已经取得用户同意</li> |
|||
/// 以上三个值,任何一个为false都会造成地图插件不工作(白屏情况) |
|||
/// |
|||
/// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy |
|||
/// |
|||
/// [AssertionError] will be thrown if [initialCameraPosition] is null; |
|||
const AMapWidget({ |
|||
Key? key, |
|||
this.privacyStatement, |
|||
this.apiKey, |
|||
this.initialCameraPosition = const CameraPosition( |
|||
target: LatLng(39.909187, 116.397451), |
|||
zoom: 10, |
|||
), |
|||
this.mapType = MapType.normal, |
|||
this.buildingsEnabled = true, |
|||
this.compassEnabled = false, |
|||
this.labelsEnabled = true, |
|||
this.limitBounds, |
|||
this.minMaxZoomPreference, |
|||
this.rotateGesturesEnabled = true, |
|||
this.scaleEnabled = true, |
|||
this.scrollGesturesEnabled = true, |
|||
this.tiltGesturesEnabled = true, |
|||
this.touchPoiEnabled = true, |
|||
this.trafficEnabled = false, |
|||
this.zoomGesturesEnabled = true, |
|||
this.onMapCreated, |
|||
this.gestureRecognizers = const <Factory<OneSequenceGestureRecognizer>>{}, |
|||
this.customStyleOptions, |
|||
this.myLocationStyleOptions, |
|||
this.onCameraMove, |
|||
this.onCameraMoveEnd, |
|||
this.onLocationChanged, |
|||
this.onTap, |
|||
this.onLongPress, |
|||
this.onPoiTouched, |
|||
this.markers = const <Marker>{}, |
|||
this.polylines = const <Polyline>{}, |
|||
this.polygons = const <Polygon>{}, |
|||
}) : super(key: key); |
|||
|
|||
/// |
|||
@override |
|||
State<StatefulWidget> createState() => _MapState(); |
|||
} |
|||
|
|||
class _MapState extends State<AMapWidget> { |
|||
Map<String, Marker> _markers = <String, Marker>{}; |
|||
Map<String, Polyline> _polylines = <String, Polyline>{}; |
|||
Map<String, Polygon> _polygons = <String, Polygon>{}; |
|||
|
|||
final Completer<AMapController> _controller = Completer<AMapController>(); |
|||
late _AMapOptions _mapOptions; |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
AMapUtil.init(context); |
|||
final Map<String, dynamic> creationParams = <String, dynamic>{ |
|||
'privacyStatement': widget.privacyStatement?.toMap(), |
|||
'apiKey': widget.apiKey?.toMap(), |
|||
'initialCameraPosition': widget.initialCameraPosition.toMap(), |
|||
'options': _mapOptions.toMap(), |
|||
'markersToAdd': serializeOverlaySet(widget.markers), |
|||
'polylinesToAdd': serializeOverlaySet(widget.polylines), |
|||
'polygonsToAdd': serializeOverlaySet(widget.polygons), |
|||
}; |
|||
Widget mapView = _methodChannel.buildView( |
|||
creationParams, |
|||
widget.gestureRecognizers, |
|||
onPlatformViewCreated, |
|||
); |
|||
return mapView; |
|||
} |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_mapOptions = _AMapOptions.fromWidget(widget); |
|||
_markers = keyByMarkerId(widget.markers); |
|||
_polygons = keyByPolygonId(widget.polygons); |
|||
_polylines = keyByPolylineId(widget.polylines); |
|||
print('initState AMapWidget'); |
|||
} |
|||
|
|||
@override |
|||
void dispose() async { |
|||
super.dispose(); |
|||
AMapController controller = await _controller.future; |
|||
controller.disponse(); |
|||
print('dispose AMapWidget with mapId: ${controller.mapId}'); |
|||
} |
|||
|
|||
@override |
|||
void reassemble() { |
|||
super.reassemble(); |
|||
print('reassemble AMapWidget'); |
|||
} |
|||
|
|||
@override |
|||
void deactivate() async { |
|||
super.deactivate(); |
|||
print('deactivate AMapWidget}'); |
|||
} |
|||
|
|||
@override |
|||
void didUpdateWidget(covariant AMapWidget oldWidget) { |
|||
super.didUpdateWidget(oldWidget); |
|||
_updateOptions(); |
|||
_updateMarkers(); |
|||
_updatePolylines(); |
|||
_updatePolygons(); |
|||
} |
|||
|
|||
Future<void> onPlatformViewCreated(int id) async { |
|||
final AMapController controller = await AMapController.init( |
|||
id, |
|||
widget.initialCameraPosition, |
|||
this, |
|||
); |
|||
_controller.complete(controller); |
|||
final MapCreatedCallback? _onMapCreated = widget.onMapCreated; |
|||
if (_onMapCreated != null) { |
|||
_onMapCreated(controller); |
|||
} |
|||
} |
|||
|
|||
void onMarkerTap(String markerId) { |
|||
final Marker? _marker = _markers[markerId]; |
|||
if (_marker != null) { |
|||
final ArgumentCallback<String>? _onTap = _marker.onTap; |
|||
if (_onTap != null) { |
|||
_onTap(markerId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void onMarkerDragEnd(String markerId, LatLng position) { |
|||
final Marker? _marker = _markers[markerId]; |
|||
if (_marker != null) { |
|||
final MarkerDragEndCallback? _onDragEnd = _marker.onDragEnd; |
|||
if (_onDragEnd != null) { |
|||
_onDragEnd(markerId, position); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void onPolylineTap(String polylineId) { |
|||
final Polyline? _polyline = _polylines[polylineId]; |
|||
if (_polyline != null) { |
|||
final ArgumentCallback<String>? _onTap = _polyline.onTap; |
|||
if (_onTap != null) { |
|||
_onTap(polylineId); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void _updateOptions() async { |
|||
final _AMapOptions newOptions = _AMapOptions.fromWidget(widget); |
|||
final Map<String, dynamic> updates = _mapOptions._updatesMap(newOptions); |
|||
if (updates.isEmpty) { |
|||
return; |
|||
} |
|||
final AMapController controller = await _controller.future; |
|||
// ignore: unawaited_futures |
|||
controller._updateMapOptions(updates); |
|||
_mapOptions = newOptions; |
|||
} |
|||
|
|||
void _updateMarkers() async { |
|||
final AMapController controller = await _controller.future; |
|||
// ignore: unawaited_futures |
|||
controller._updateMarkers( |
|||
MarkerUpdates.from(_markers.values.toSet(), widget.markers), |
|||
); |
|||
_markers = keyByMarkerId(widget.markers); |
|||
} |
|||
|
|||
void _updatePolylines() async { |
|||
final AMapController controller = await _controller.future; |
|||
controller._updatePolylines( |
|||
PolylineUpdates.from(_polylines.values.toSet(), widget.polylines), |
|||
); |
|||
_polylines = keyByPolylineId(widget.polylines); |
|||
} |
|||
|
|||
void _updatePolygons() async { |
|||
final AMapController controller = await _controller.future; |
|||
controller._updatePolygons( |
|||
PolygonUpdates.from(_polygons.values.toSet(), widget.polygons), |
|||
); |
|||
_polygons = keyByPolygonId(widget.polygons); |
|||
} |
|||
} |
|||
|
|||
//高德地图参数设置 |
|||
class _AMapOptions { |
|||
///地图类型 |
|||
final MapType mapType; |
|||
|
|||
///自定义地图样式 |
|||
final CustomStyleOptions? customStyleOptions; |
|||
|
|||
///定位小蓝点 |
|||
final MyLocationStyleOptions? myLocationStyleOptions; |
|||
|
|||
//缩放级别范围 |
|||
final MinMaxZoomPreference? minMaxZoomPreference; |
|||
|
|||
///地图显示范围 |
|||
final LatLngBounds? limitBounds; |
|||
|
|||
///显示路况开关 |
|||
final bool? trafficEnabled; |
|||
|
|||
/// 地图poi是否允许点击 |
|||
final bool? touchPoiEnabled; |
|||
|
|||
///是否显示3D建筑物 |
|||
final bool? buildingsEnabled; |
|||
|
|||
///是否显示底图文字标注 |
|||
final bool? labelsEnabled; |
|||
|
|||
///是否显示指南针 |
|||
final bool? compassEnabled; |
|||
|
|||
///是否显示比例尺 |
|||
final bool? scaleEnabled; |
|||
|
|||
///是否支持缩放手势 |
|||
final bool? zoomGesturesEnabled; |
|||
|
|||
///是否支持滑动手势 |
|||
final bool? scrollGesturesEnabled; |
|||
|
|||
///是否支持旋转手势 |
|||
final bool? rotateGesturesEnabled; |
|||
|
|||
///是否支持仰角手势 |
|||
final bool? tiltGesturesEnabled; |
|||
|
|||
_AMapOptions({ |
|||
this.mapType = MapType.normal, |
|||
this.buildingsEnabled, |
|||
this.customStyleOptions, |
|||
this.myLocationStyleOptions, |
|||
this.compassEnabled, |
|||
this.labelsEnabled, |
|||
this.limitBounds, |
|||
this.minMaxZoomPreference, |
|||
this.scaleEnabled, |
|||
this.touchPoiEnabled, |
|||
this.trafficEnabled, |
|||
this.rotateGesturesEnabled, |
|||
this.scrollGesturesEnabled, |
|||
this.tiltGesturesEnabled, |
|||
this.zoomGesturesEnabled, |
|||
}); |
|||
|
|||
static _AMapOptions fromWidget(AMapWidget map) { |
|||
return _AMapOptions( |
|||
mapType: map.mapType, |
|||
buildingsEnabled: map.buildingsEnabled, |
|||
compassEnabled: map.compassEnabled, |
|||
labelsEnabled: map.labelsEnabled, |
|||
limitBounds: map.limitBounds, |
|||
minMaxZoomPreference: map.minMaxZoomPreference, |
|||
scaleEnabled: map.scaleEnabled, |
|||
touchPoiEnabled: map.touchPoiEnabled, |
|||
trafficEnabled: map.trafficEnabled, |
|||
rotateGesturesEnabled: map.rotateGesturesEnabled, |
|||
scrollGesturesEnabled: map.scrollGesturesEnabled, |
|||
tiltGesturesEnabled: map.tiltGesturesEnabled, |
|||
zoomGesturesEnabled: map.zoomGesturesEnabled, |
|||
customStyleOptions: map.customStyleOptions?.clone(), |
|||
myLocationStyleOptions: map.myLocationStyleOptions?.clone(), |
|||
); |
|||
} |
|||
|
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> optionsMap = <String, dynamic>{}; |
|||
void addIfNonNull(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
optionsMap[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfNonNull('mapType', mapType.index); |
|||
addIfNonNull('buildingsEnabled', buildingsEnabled); |
|||
addIfNonNull('customStyleOptions', customStyleOptions?.clone().toMap()); |
|||
addIfNonNull('compassEnabled', compassEnabled); |
|||
addIfNonNull('labelsEnabled', labelsEnabled); |
|||
addIfNonNull('limitBounds', limitBounds?.toJson()); |
|||
addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?.toJson()); |
|||
addIfNonNull('scaleEnabled', scaleEnabled); |
|||
addIfNonNull('touchPoiEnabled', touchPoiEnabled); |
|||
addIfNonNull('trafficEnabled', trafficEnabled); |
|||
addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled); |
|||
addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled); |
|||
addIfNonNull('tiltGesturesEnabled', tiltGesturesEnabled); |
|||
addIfNonNull('zoomGesturesEnabled', zoomGesturesEnabled); |
|||
addIfNonNull('myLocationStyle', myLocationStyleOptions?.clone().toMap()); |
|||
return optionsMap; |
|||
} |
|||
|
|||
Map<String, dynamic> _updatesMap(_AMapOptions newOptions) { |
|||
final Map<String, dynamic> prevOptionsMap = toMap(); |
|||
|
|||
return newOptions.toMap()..removeWhere( |
|||
(String key, dynamic value) => |
|||
(_checkChange(key, prevOptionsMap[key], value)), |
|||
); |
|||
} |
|||
|
|||
bool _checkChange(String key, dynamic preValue, dynamic newValue) { |
|||
if (key == 'myLocationStyle' || key == 'customStyleOptions') { |
|||
return preValue?.toString() == newValue?.toString(); |
|||
} else { |
|||
return preValue == newValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:flutter/gestures.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
|||
|
|||
/// “amap_flutter_map”平台特定实现必须扩展的接口 |
|||
abstract class AMapFlutterPlatform extends PlatformInterface { |
|||
static final Object _token = Object(); |
|||
AMapFlutterPlatform() : super(token: _token); |
|||
static AMapFlutterPlatform _instance = MethodChannelAMapFlutterMap() as AMapFlutterPlatform; |
|||
|
|||
/// The default instance of [AMapFlutterPlatform] to use. |
|||
/// |
|||
/// Defaults to [MethodChannelAMapFlutterMap]. |
|||
static AMapFlutterPlatform get instance => _instance; |
|||
|
|||
static set instance(AMapFlutterPlatform instance) { |
|||
PlatformInterface.verifyToken(instance, _token); |
|||
_instance = instance; |
|||
} |
|||
|
|||
/// /// Initializes the platform interface with [id]. |
|||
/// |
|||
/// This method is called when the plugin is first initialized. |
|||
Future<void> init(int id) { |
|||
throw UnimplementedError('init() has not been implemented.'); |
|||
} |
|||
|
|||
void dispose({required int id}) { |
|||
throw UnimplementedError('dispose() has not been implemented.'); |
|||
} |
|||
|
|||
Widget buildView( |
|||
Map<String, dynamic> creationParams, |
|||
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, |
|||
PlatformViewCreatedCallback onPlatformViewCreated) { |
|||
throw UnimplementedError('buildView() has not been implemented.'); |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'package:amap_flutter_map/amap_flutter_map.dart'; |
|||
|
|||
///地图事件处理 |
|||
class MapEvent<T> { |
|||
/// 地图id |
|||
final int mapId; |
|||
|
|||
///返回的内容,对应的[MethodCall]中的[[arguments]] |
|||
final T value; |
|||
|
|||
/// 构造一个event |
|||
/// |
|||
/// `mapId` 当前地图的id |
|||
/// `value` 需要传输的值,可以为`null`. |
|||
MapEvent(this.mapId, this.value); |
|||
} |
|||
|
|||
///定位回调接口 |
|||
class LocationChangedEvent extends MapEvent<AMapLocation> { |
|||
LocationChangedEvent(int mapId, AMapLocation value) : super(mapId, value); |
|||
} |
|||
|
|||
///地图移动回调 |
|||
class CameraPositionMoveEvent extends MapEvent<CameraPosition> { |
|||
CameraPositionMoveEvent(int mapId, CameraPosition value) |
|||
: super(mapId, value); |
|||
} |
|||
|
|||
///地图移动结束回调 |
|||
class CameraPositionMoveEndEvent extends MapEvent<CameraPosition> { |
|||
CameraPositionMoveEndEvent(int mapId, CameraPosition value) |
|||
: super(mapId, value); |
|||
} |
|||
|
|||
///点击地图回调 |
|||
class MapTapEvent extends MapEvent<LatLng> { |
|||
MapTapEvent(int mapId, LatLng value) : super(mapId, value); |
|||
} |
|||
|
|||
///长按地图回调 |
|||
class MapLongPressEvent extends MapEvent<LatLng> { |
|||
MapLongPressEvent(int mapId, LatLng value) : super(mapId, value); |
|||
} |
|||
|
|||
/// 带位置回调的地图事件 |
|||
class _PositionedMapEvent<T> extends MapEvent<T> { |
|||
/// 事件中带的位置信息 |
|||
final LatLng position; |
|||
|
|||
/// 构造一个带位置的地图事件, |
|||
/// |
|||
/// `mapId` 当前地图的id |
|||
/// `value` 需要传输的值,可以为`null`. |
|||
_PositionedMapEvent(int mapId, this.position, T value) : super(mapId, value); |
|||
} |
|||
|
|||
/// [Marker] 的点击事件 |
|||
class MarkerTapEvent extends MapEvent<String> { |
|||
MarkerTapEvent(int mapId, String markerId) : super(mapId, markerId); |
|||
} |
|||
|
|||
/// [Marker] 的拖拽结束事件,附带拖拽结束时的位置信息[LatLng]. |
|||
class MarkerDragEndEvent extends _PositionedMapEvent<String> { |
|||
MarkerDragEndEvent(int mapId, LatLng position, String markerId) |
|||
: super(mapId, position, markerId); |
|||
} |
|||
|
|||
/// [Polyline] 的点击事件 |
|||
class PolylineTapEvent extends MapEvent<String> { |
|||
PolylineTapEvent(int mapId, String polylineId) : super(mapId, polylineId); |
|||
} |
|||
|
|||
/// Poi点击事件 |
|||
class MapPoiTouchEvent extends MapEvent<AMapPoi> { |
|||
MapPoiTouchEvent(int mapId, AMapPoi poi) : super(mapId, poi); |
|||
} |
|||
@ -0,0 +1,274 @@ |
|||
import 'dart:async'; |
|||
import 'dart:typed_data'; |
|||
|
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; |
|||
import 'package:amap_flutter_map/src/types/types.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:flutter/gestures.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:stream_transform/stream_transform.dart'; |
|||
|
|||
import 'map_event.dart'; |
|||
|
|||
const VIEW_TYPE = 'com.amap.flutter.map'; |
|||
|
|||
/// 使用[MethodChannel]与Native代码通信的[AMapFlutterPlatform]的实现。 |
|||
class MethodChannelAMapFlutterMap implements AMapFlutterPlatform { |
|||
final Map<int, MethodChannel> _channels = {}; |
|||
|
|||
MethodChannel channel(int mapId) { |
|||
return _channels[mapId]!; |
|||
} |
|||
|
|||
@override |
|||
Future<void> init(int mapId) { |
|||
MethodChannel? channel = _channels[mapId]; |
|||
if (channel == null) { |
|||
channel = MethodChannel('amap_flutter_map_$mapId'); |
|||
channel.setMethodCallHandler((call) => _handleMethodCall(call, mapId)); |
|||
_channels[mapId] = channel; |
|||
} |
|||
return channel.invokeMethod<void>('map#waitForMap'); |
|||
} |
|||
|
|||
///更新地图参数 |
|||
Future<void> updateMapOptions( |
|||
Map<String, dynamic> newOptions, { |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<void>( |
|||
'map#update', |
|||
<String, dynamic>{ |
|||
'options': newOptions, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/// 更新Marker的数据 |
|||
Future<void> updateMarkers( |
|||
MarkerUpdates markerUpdates, { |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<void>( |
|||
'markers#update', |
|||
markerUpdates.toMap(), |
|||
); |
|||
} |
|||
|
|||
/// 更新polyline的数据 |
|||
Future<void> updatePolylines( |
|||
PolylineUpdates polylineUpdates, { |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<void>( |
|||
'polylines#update', |
|||
polylineUpdates.toMap(), |
|||
); |
|||
} |
|||
|
|||
/// 更新polygon的数据 |
|||
Future<void> updatePolygons( |
|||
PolygonUpdates polygonUpdates, { |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<void>( |
|||
'polygons#update', |
|||
polygonUpdates.toMap(), |
|||
); |
|||
} |
|||
|
|||
@override |
|||
void dispose({required int id}) { |
|||
if (_channels.containsKey(id)) { |
|||
_channels.remove(id); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Widget buildView( |
|||
Map<String, dynamic> creationParams, |
|||
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, |
|||
void Function(int id) onPlatformViewCreated) { |
|||
if (defaultTargetPlatform == TargetPlatform.android) { |
|||
creationParams['debugMode'] = kDebugMode; |
|||
return AndroidView( |
|||
viewType: VIEW_TYPE, |
|||
onPlatformViewCreated: onPlatformViewCreated, |
|||
gestureRecognizers: gestureRecognizers, |
|||
creationParams: creationParams, |
|||
creationParamsCodec: const StandardMessageCodec(), |
|||
); |
|||
} else if (defaultTargetPlatform == TargetPlatform.iOS) { |
|||
return UiKitView( |
|||
viewType: VIEW_TYPE, |
|||
onPlatformViewCreated: onPlatformViewCreated, |
|||
gestureRecognizers: gestureRecognizers, |
|||
creationParams: creationParams, |
|||
creationParamsCodec: const StandardMessageCodec(), |
|||
); |
|||
} |
|||
return Text('当前平台:$defaultTargetPlatform, 不支持使用高德地图插件'); |
|||
} |
|||
|
|||
// handleMethodCall的`broadcast` |
|||
final StreamController<MapEvent> _mapEventStreamController = |
|||
StreamController<MapEvent>.broadcast(); |
|||
|
|||
// 根据mapid返回相应的event. |
|||
Stream<MapEvent> _events(int mapId) => |
|||
_mapEventStreamController.stream.where((event) => event.mapId == mapId); |
|||
|
|||
//定位回调 |
|||
Stream<LocationChangedEvent> onLocationChanged({required int mapId}) { |
|||
return _events(mapId).whereType<LocationChangedEvent>(); |
|||
} |
|||
|
|||
//Camera 移动回调 |
|||
Stream<CameraPositionMoveEvent> onCameraMove({required int mapId}) { |
|||
return _events(mapId).whereType<CameraPositionMoveEvent>(); |
|||
} |
|||
|
|||
///Camera 移动结束回调 |
|||
Stream<CameraPositionMoveEndEvent> onCameraMoveEnd({required int mapId}) { |
|||
return _events(mapId).whereType<CameraPositionMoveEndEvent>(); |
|||
} |
|||
|
|||
Stream<MapTapEvent> onMapTap({required int mapId}) { |
|||
return _events(mapId).whereType<MapTapEvent>(); |
|||
} |
|||
|
|||
Stream<MapLongPressEvent> onMapLongPress({required int mapId}) { |
|||
return _events(mapId).whereType<MapLongPressEvent>(); |
|||
} |
|||
|
|||
Stream<MapPoiTouchEvent> onPoiTouched({required int mapId}) { |
|||
return _events(mapId).whereType<MapPoiTouchEvent>(); |
|||
} |
|||
|
|||
Stream<MarkerTapEvent> onMarkerTap({required int mapId}) { |
|||
return _events(mapId).whereType<MarkerTapEvent>(); |
|||
} |
|||
|
|||
Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) { |
|||
return _events(mapId).whereType<MarkerDragEndEvent>(); |
|||
} |
|||
|
|||
Stream<PolylineTapEvent> onPolylineTap({required int mapId}) { |
|||
return _events(mapId).whereType<PolylineTapEvent>(); |
|||
} |
|||
|
|||
Future<dynamic> _handleMethodCall(MethodCall call, int mapId) async { |
|||
switch (call.method) { |
|||
case 'location#changed': |
|||
try { |
|||
_mapEventStreamController.add(LocationChangedEvent( |
|||
mapId, AMapLocation.fromMap(call.arguments['location'])!)); |
|||
} catch (e) { |
|||
print("location#changed error=======>" + e.toString()); |
|||
} |
|||
break; |
|||
|
|||
case 'camera#onMove': |
|||
try { |
|||
_mapEventStreamController.add(CameraPositionMoveEvent( |
|||
mapId, CameraPosition.fromMap(call.arguments['position'])!)); |
|||
} catch (e) { |
|||
print("camera#onMove error===>" + e.toString()); |
|||
} |
|||
break; |
|||
case 'camera#onMoveEnd': |
|||
try { |
|||
_mapEventStreamController.add(CameraPositionMoveEndEvent( |
|||
mapId, CameraPosition.fromMap(call.arguments['position'])!)); |
|||
} catch (e) { |
|||
print("camera#onMoveEnd error===>" + e.toString()); |
|||
} |
|||
break; |
|||
case 'map#onTap': |
|||
_mapEventStreamController |
|||
.add(MapTapEvent(mapId, LatLng.fromJson(call.arguments['latLng'])!)); |
|||
break; |
|||
case 'map#onLongPress': |
|||
_mapEventStreamController.add(MapLongPressEvent( |
|||
mapId, LatLng.fromJson(call.arguments['latLng'])!)); |
|||
break; |
|||
|
|||
case 'marker#onTap': |
|||
_mapEventStreamController.add(MarkerTapEvent( |
|||
mapId, |
|||
call.arguments['markerId'], |
|||
)); |
|||
break; |
|||
case 'marker#onDragEnd': |
|||
_mapEventStreamController.add(MarkerDragEndEvent( |
|||
mapId, |
|||
LatLng.fromJson(call.arguments['position'])!, |
|||
call.arguments['markerId'])); |
|||
break; |
|||
case 'polyline#onTap': |
|||
_mapEventStreamController |
|||
.add(PolylineTapEvent(mapId, call.arguments['polylineId'])); |
|||
break; |
|||
case 'map#onPoiTouched': |
|||
try { |
|||
_mapEventStreamController.add( |
|||
MapPoiTouchEvent(mapId, AMapPoi.fromJson(call.arguments['poi'])!)); |
|||
} catch (e) { |
|||
print('map#onPoiTouched error===>' + e.toString()); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
///移动镜头到一个新的位置 |
|||
Future<void> moveCamera( |
|||
CameraUpdate cameraUpdate, { |
|||
required int mapId, |
|||
bool animated = true, |
|||
int duration = 0, |
|||
}) { |
|||
return channel(mapId).invokeMethod<void>('camera#move', <String, dynamic>{ |
|||
'cameraUpdate': cameraUpdate.toJson(), |
|||
'animated': animated, |
|||
'duration': duration |
|||
}); |
|||
} |
|||
|
|||
///设置地图每秒渲染的帧数 |
|||
Future<void> setRenderFps(int fps, {required int mapId}) { |
|||
return channel(mapId) |
|||
.invokeMethod<void>('map#setRenderFps', <String, dynamic>{ |
|||
'fps': fps, |
|||
}); |
|||
} |
|||
|
|||
///截屏 |
|||
Future<Uint8List?> takeSnapshot({ |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot'); |
|||
} |
|||
|
|||
//获取地图审图号(普通地图) |
|||
Future<String?> getMapContentApprovalNumber({ |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<String>('map#contentApprovalNumber'); |
|||
} |
|||
|
|||
//获取地图审图号(卫星地图) |
|||
Future<String?> getSatelliteImageApprovalNumber({ |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId) |
|||
.invokeMethod<String>('map#satelliteImageApprovalNumber'); |
|||
} |
|||
|
|||
Future<void> clearDisk({ |
|||
required int mapId, |
|||
}) { |
|||
return channel(mapId).invokeMethod<void>('map#clearDisk'); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/// 地图覆盖物基类 |
|||
class BaseOverlay { |
|||
/// overlay id |
|||
late String _id; |
|||
|
|||
String get id => _id; |
|||
|
|||
BaseOverlay() { |
|||
this._id = this.hashCode.toString(); |
|||
} |
|||
|
|||
void setIdForCopy(String copyId) => _id = copyId; |
|||
|
|||
BaseOverlay clone() { |
|||
throw UnimplementedError( |
|||
'BaseOverlay subClass should implement this methed.'); |
|||
} |
|||
|
|||
Map<String, dynamic> toMap() { |
|||
throw UnimplementedError( |
|||
'BaseOverlay subClass should implement this methed.'); |
|||
} |
|||
} |
|||
|
|||
List<Map<String, dynamic>>? serializeOverlaySet(Set<BaseOverlay> overlays) { |
|||
return overlays |
|||
.map<Map<String, dynamic>>((BaseOverlay overlay) => overlay.toMap()) |
|||
.toList(); |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
// Copyright 2018 The Chromium Authors. All rights reserved. |
|||
// Use of this source code is governed by a BSD-style license that can be |
|||
// found in the LICENSE file. |
|||
|
|||
import 'dart:async' show Future; |
|||
import 'dart:typed_data' show Uint8List; |
|||
import 'dart:ui'; |
|||
|
|||
import 'package:amap_flutter_base/amap_flutter_base.dart' show AMapUtil; |
|||
import 'package:flutter/material.dart' |
|||
show ImageConfiguration, AssetImage, AssetBundleImageKey; |
|||
import 'package:flutter/services.dart' show AssetBundle; |
|||
|
|||
import 'package:flutter/foundation.dart' show kIsWeb; |
|||
|
|||
/// Bitmap工具类 |
|||
class BitmapDescriptor { |
|||
const BitmapDescriptor._(this._json); |
|||
|
|||
/// 红色. |
|||
static const double hueRed = 0.0; |
|||
|
|||
/// 橙色. |
|||
static const double hueOrange = 30.0; |
|||
|
|||
/// 黄色. |
|||
static const double hueYellow = 60.0; |
|||
|
|||
/// 绿色. |
|||
static const double hueGreen = 120.0; |
|||
|
|||
/// 青色. |
|||
static const double hueCyan = 180.0; |
|||
|
|||
/// 天蓝色. |
|||
static const double hueAzure = 210.0; |
|||
|
|||
/// 蓝色. |
|||
static const double hueBlue = 240.0; |
|||
|
|||
/// 紫色. |
|||
static const double hueViolet = 270.0; |
|||
|
|||
/// 酒红色. |
|||
static const double hueMagenta = 300.0; |
|||
|
|||
/// 玫瑰红. |
|||
static const double hueRose = 330.0; |
|||
|
|||
/// 创建默认的marker 图标的 bitmap 描述信息对象. |
|||
static const BitmapDescriptor defaultMarker = |
|||
BitmapDescriptor._(<dynamic>['defaultMarker']); |
|||
|
|||
/// 创建引用默认着色的BitmapDescriptor |
|||
static BitmapDescriptor defaultMarkerWithHue(double hue) { |
|||
assert(0.0 <= hue && hue < 360.0); |
|||
String filename = "BLUE.png"; |
|||
if (hue == hueRed) { |
|||
filename = "RED.png"; |
|||
} else if (hue == hueOrange) { |
|||
filename = "ORANGE.png"; |
|||
} else if (hue == hueYellow) { |
|||
filename = "YELLOW.png"; |
|||
} else if (hue == hueGreen) { |
|||
filename = "GREEN.png"; |
|||
} else if (hue == hueCyan) { |
|||
filename = "CYAN.png"; |
|||
} else if (hue == hueAzure) { |
|||
filename = "AZURE.png"; |
|||
} else if (hue == hueBlue) { |
|||
filename = "BLUE.png"; |
|||
} else if (hue == hueViolet) { |
|||
filename = "VIOLET.png"; |
|||
} else if (hue == hueMagenta) { |
|||
filename = "MAGENTA.png"; |
|||
} else if (hue == hueRose) { |
|||
filename = "ROSE.png"; |
|||
} |
|||
return BitmapDescriptor._(<dynamic>[ |
|||
'fromAssetImage', |
|||
"packages/amap_flutter_map/res/$filename", |
|||
AMapUtil.devicePixelRatio |
|||
]); |
|||
} |
|||
|
|||
///根据输入的icon路径[iconPath]创建[BitmapDescriptor] |
|||
static BitmapDescriptor fromIconPath(String iconPath) { |
|||
return BitmapDescriptor._(<dynamic>[ |
|||
'fromAsset', |
|||
iconPath, |
|||
]); |
|||
} |
|||
|
|||
///从资源图像创建[BitmapDescriptor]。 |
|||
/// |
|||
///Flutter中的assert的资产图像按以下方式存储: |
|||
/// https://flutter.dev/docs/development/ui/assets and images声明-分辨率感知图像资源 |
|||
///该方法考虑了各种资产解决方案 |
|||
///并根据dpi将图像缩放到正确的分辨率。 |
|||
///将`mipmaps1设置为false可加载图像的精确dpi版本,默认情况下,`mipmap`为true。 |
|||
static Future<BitmapDescriptor> fromAssetImage( |
|||
ImageConfiguration configuration, |
|||
String assetName, { |
|||
AssetBundle? bundle, |
|||
String? package, |
|||
bool mipmaps = true, |
|||
}) async { |
|||
if (!mipmaps && configuration.devicePixelRatio != null) { |
|||
return BitmapDescriptor._(<dynamic>[ |
|||
'fromAssetImage', |
|||
assetName, |
|||
configuration.devicePixelRatio, |
|||
]); |
|||
} |
|||
final AssetImage assetImage = |
|||
AssetImage(assetName, package: package, bundle: bundle); |
|||
final AssetBundleImageKey assetBundleImageKey = |
|||
await assetImage.obtainKey(configuration); |
|||
final Size? size = configuration.size; |
|||
return BitmapDescriptor._(<dynamic>[ |
|||
'fromAssetImage', |
|||
assetBundleImageKey.name, |
|||
assetBundleImageKey.scale, |
|||
if (kIsWeb && size != null) |
|||
[ |
|||
size.width, |
|||
size.height, |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/// 根据将PNG图片转换后的二进制数据[byteData]创建BitmapDescriptor |
|||
static BitmapDescriptor fromBytes(Uint8List byteData) { |
|||
return BitmapDescriptor._(<dynamic>['fromBytes', byteData]); |
|||
} |
|||
|
|||
final dynamic _json; |
|||
|
|||
dynamic toMap() => _json; |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'package:flutter/cupertino.dart'; |
|||
|
|||
/// 相机位置,包含可视区域的位置参数。 |
|||
class CameraPosition { |
|||
/// 构造一个CameraPosition 对象 |
|||
/// |
|||
/// 如果[bearing], [target], [tilt], 或者 [zoom] 为null时会返回[AssertionError] |
|||
const CameraPosition({ |
|||
this.bearing = 0.0, |
|||
required this.target, |
|||
this.tilt = 0.0, |
|||
this.zoom = 10, |
|||
}); |
|||
|
|||
/// 可视区域指向的方向,以角度为单位,从正北向逆时针方向计算,从0 度到360 度。 |
|||
final double bearing; |
|||
|
|||
/// 目标位置的屏幕中心点经纬度坐标。 |
|||
final LatLng target; |
|||
|
|||
/// 目标可视区域的倾斜度,以角度为单位。范围从0到360度 |
|||
final double tilt; |
|||
|
|||
/// 目标可视区域的缩放级别 |
|||
final double zoom; |
|||
|
|||
/// 将[CameraPosition]装换成Map |
|||
/// |
|||
/// 主要在插件内部使用 |
|||
dynamic toMap() => <String, dynamic>{ |
|||
'bearing': bearing, |
|||
'target': target.toJson(), |
|||
'tilt': tilt, |
|||
'zoom': zoom, |
|||
}; |
|||
|
|||
/// 从Map转换成[CameraPosition] |
|||
/// |
|||
/// 主要在插件内部使用 |
|||
static CameraPosition? fromMap(dynamic json) { |
|||
if (json == null || !(json is Map<dynamic, dynamic>)) { |
|||
return null; |
|||
} |
|||
final LatLng? target = LatLng.fromJson(json['target']); |
|||
if (target == null) { |
|||
return null; |
|||
} |
|||
return CameraPosition( |
|||
bearing: json['bearing'], |
|||
target: target, |
|||
tilt: json['tilt'], |
|||
zoom: json['zoom'], |
|||
); |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(dynamic other) { |
|||
if (identical(this, other)) return true; |
|||
if (runtimeType != other.runtimeType) return false; |
|||
final CameraPosition typedOther = other; |
|||
return bearing == typedOther.bearing && |
|||
target == typedOther.target && |
|||
tilt == typedOther.tilt && |
|||
zoom == typedOther.zoom; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => Object.hash(bearing, target, tilt, zoom); |
|||
|
|||
@override |
|||
String toString() => |
|||
'CameraPosition(bearing: $bearing, target: $target, tilt: $tilt, zoom: $zoom)'; |
|||
} |
|||
|
|||
/// 描述地图状态将要发生的变化 |
|||
class CameraUpdate { |
|||
CameraUpdate._(this._json); |
|||
|
|||
///返回根据新的[CameraPosition]移动后的[CameraUpdate]. |
|||
/// |
|||
///主要用于改变地图的中心点、缩放级别、倾斜角、角度等信息 |
|||
static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { |
|||
return CameraUpdate._(<dynamic>[ |
|||
'newCameraPosition', |
|||
cameraPosition.toMap(), |
|||
]); |
|||
} |
|||
|
|||
///移动到一个新的位置点[latLng] |
|||
/// |
|||
///主要用于改变地图的中心点 |
|||
static CameraUpdate newLatLng(LatLng latLng) { |
|||
return CameraUpdate._(<dynamic>['newLatLng', latLng.toJson()]); |
|||
} |
|||
|
|||
///根据指定到摄像头显示范围[bounds]和边界值[padding]创建一个CameraUpdate对象 |
|||
/// |
|||
///主要用于根据指定的显示范围[bounds]以最佳的视野显示地图 |
|||
static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { |
|||
return CameraUpdate._(<dynamic>[ |
|||
'newLatLngBounds', |
|||
bounds.toJson(), |
|||
padding, |
|||
]); |
|||
} |
|||
|
|||
/// 根据指定的新的位置[latLng]和缩放级别[zoom]创建一个CameraUpdate对象 |
|||
/// |
|||
/// 主要用于同时改变中心点和缩放级别 |
|||
static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { |
|||
return CameraUpdate._(<dynamic>['newLatLngZoom', latLng.toJson(), zoom]); |
|||
} |
|||
|
|||
/// 按照指定到像素点[dx]和[dy]移动地图中心点 |
|||
/// |
|||
/// [dx]是水平移动的像素数。正值代表可视区域向右移动,负值代表可视区域向左移动 |
|||
/// |
|||
/// [dy]是垂直移动的像素数。正值代表可视区域向下移动,负值代表可视区域向上移动 |
|||
/// |
|||
/// 返回包含x,y方向上移动像素数的cameraUpdate对象。 |
|||
static CameraUpdate scrollBy(double dx, double dy) { |
|||
return CameraUpdate._(<dynamic>['scrollBy', dx, dy]); |
|||
} |
|||
|
|||
/// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 |
|||
/// |
|||
///主要用于放大地图缩放级别,在当前地图显示的级别基础上加1 |
|||
static CameraUpdate zoomIn() { |
|||
return CameraUpdate._(<dynamic>['zoomIn']); |
|||
} |
|||
|
|||
/// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 |
|||
/// |
|||
/// 主要用于减少地图缩放级别,在当前地图显示的级别基础上减1 |
|||
static CameraUpdate zoomOut() { |
|||
return CameraUpdate._(<dynamic>['zoomOut']); |
|||
} |
|||
|
|||
/// 创建一个指定缩放级别[zoom]的CameraUpdate对象 |
|||
/// |
|||
/// 主要用于设置地图缩放级别 |
|||
static CameraUpdate zoomTo(double zoom) { |
|||
return CameraUpdate._(<dynamic>['zoomTo', zoom]); |
|||
} |
|||
|
|||
final dynamic _json; |
|||
|
|||
dynamic toJson() => _json; |
|||
} |
|||
@ -0,0 +1,249 @@ |
|||
// Copyright 2018 The Chromium Authors. All rights reserved. |
|||
// Use of this source code is governed by a BSD-style license that can be |
|||
// found in the LICENSE file. |
|||
|
|||
import 'dart:ui' show Offset; |
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'base_overlay.dart'; |
|||
import 'bitmap.dart'; |
|||
|
|||
/// Marker拖动回调 |
|||
typedef void MarkerDragEndCallback(String id, LatLng endPosition); |
|||
|
|||
///Marker的气泡 |
|||
/// |
|||
///Android和iOS的实现机制有差异,仅在接口层面拉齐,效果一致 |
|||
class InfoWindow { |
|||
/// 为 [Marker] 产生一个不可修改的文本气泡. |
|||
const InfoWindow({this.title, this.snippet}); |
|||
|
|||
/// 无文本的气泡 |
|||
static const InfoWindow noText = InfoWindow(); |
|||
|
|||
/// 气泡的title |
|||
final String? title; |
|||
|
|||
/// 气泡的详细信息 |
|||
final String? snippet; |
|||
|
|||
/// 气泡copy方法 |
|||
/// |
|||
InfoWindow copyWith({String? titleParam, String? snippetParam}) { |
|||
return InfoWindow( |
|||
title: titleParam ?? title, |
|||
snippet: snippetParam ?? snippet, |
|||
); |
|||
} |
|||
|
|||
Map<String, dynamic> _toMap() { |
|||
final Map<String, dynamic> json = <String, dynamic>{}; |
|||
|
|||
void addIfPresent(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
json[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfPresent('title', title); |
|||
addIfPresent('snippet', snippet); |
|||
return json; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is! InfoWindow) { |
|||
return false; |
|||
} |
|||
final InfoWindow typedOther = other; |
|||
return title == typedOther.title && snippet == typedOther.snippet; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => Object.hash(title, snippet); |
|||
|
|||
@override |
|||
String toString() { |
|||
return 'InfoWindow{title: $title, snippet: $snippet}'; |
|||
} |
|||
} |
|||
|
|||
/// 点覆盖物的类 |
|||
class Marker extends BaseOverlay { |
|||
Marker({ |
|||
required this.position, |
|||
double alpha = 1.0, |
|||
Offset anchor = const Offset(0.5, 1.0), |
|||
this.clickable = true, |
|||
this.draggable = false, |
|||
this.icon = BitmapDescriptor.defaultMarker, |
|||
this.infoWindowEnable = true, |
|||
this.infoWindow = InfoWindow.noText, |
|||
this.rotation = 0.0, |
|||
this.visible = true, |
|||
this.zIndex = 0.0, |
|||
this.onTap, |
|||
this.onDragEnd, |
|||
}) : this.alpha = |
|||
// ignore: unnecessary_null_comparison |
|||
(alpha != null ? (alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)) : alpha), |
|||
// ignore: unnecessary_null_comparison |
|||
this.anchor = |
|||
(anchor == null |
|||
? Offset(0.5, 1.0) |
|||
: ((anchor.dx < 0 || |
|||
anchor.dx > 1 || |
|||
anchor.dy < 0 || |
|||
anchor.dy > 1) |
|||
? Offset(0.5, 1.0) |
|||
: anchor)), |
|||
super(); |
|||
|
|||
/// 透明度 |
|||
final double alpha; |
|||
|
|||
/// 覆盖物视图相对地图上的经纬度位置的锚点 |
|||
final Offset anchor; |
|||
|
|||
/// 是否可点击,默认为true |
|||
final bool clickable; |
|||
|
|||
/// 是否可拖拽,默认为false |
|||
final bool draggable; |
|||
|
|||
/// 覆盖物的图标 |
|||
final BitmapDescriptor icon; |
|||
|
|||
/// 是否显示气泡,如果为true,则点击[Marker]后,会显示该气泡[InfoWindow] |
|||
/// 如果为false,则始终不会显示该气泡 |
|||
final bool infoWindowEnable; |
|||
|
|||
/// 覆盖物上的气泡,当被点击时,如果infoWindowEnable为true,则会显示出来 |
|||
final InfoWindow infoWindow; |
|||
|
|||
/// 位置,不能为空 |
|||
final LatLng position; |
|||
|
|||
/// 旋转角度,以锚点为中心,顺时针旋转(单位:度数) |
|||
/// |
|||
/// 注意:iOS端目前仅支持绕marker中心点旋转 |
|||
final double rotation; |
|||
|
|||
/// 是否可见 |
|||
final bool visible; |
|||
|
|||
/// z轴的值,用于调整该覆盖物的相对绘制层级关系 |
|||
/// 值越小,图层越靠下,iOS该值不支持动态修改,仅能在初始化时指定 |
|||
final double zIndex; |
|||
|
|||
/// 回调的参数是对应的id |
|||
final ArgumentCallback<String>? onTap; |
|||
|
|||
/// Marker被拖拽结束的回调 |
|||
final MarkerDragEndCallback? onDragEnd; |
|||
|
|||
/// copy的真正复制的参数,主要用于需要修改某个属性参数时使用 |
|||
Marker copyWith({ |
|||
double? alphaParam, |
|||
Offset? anchorParam, |
|||
bool? clickableParam, |
|||
bool? draggableParam, |
|||
BitmapDescriptor? iconParam, |
|||
bool? infoWindowEnableParam, |
|||
InfoWindow? infoWindowParam, |
|||
LatLng? positionParam, |
|||
double? rotationParam, |
|||
bool? visibleParam, |
|||
ArgumentCallback<String?>? onTapParam, |
|||
MarkerDragEndCallback? onDragEndParam, |
|||
}) { |
|||
Marker copyMark = Marker( |
|||
alpha: alphaParam ?? alpha, |
|||
anchor: anchorParam ?? anchor, |
|||
clickable: clickableParam ?? clickable, |
|||
draggable: draggableParam ?? draggable, |
|||
icon: iconParam ?? icon, |
|||
infoWindowEnable: infoWindowEnableParam ?? infoWindowEnable, |
|||
infoWindow: infoWindowParam ?? infoWindow, |
|||
position: positionParam ?? position, |
|||
rotation: rotationParam ?? rotation, |
|||
visible: visibleParam ?? visible, |
|||
zIndex: zIndex, |
|||
onTap: onTapParam ?? onTap, |
|||
onDragEnd: onDragEndParam ?? onDragEnd, |
|||
); |
|||
copyMark.setIdForCopy(id); |
|||
return copyMark; |
|||
} |
|||
|
|||
Marker clone() => copyWith(); |
|||
|
|||
@override |
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> json = <String, dynamic>{}; |
|||
|
|||
void addIfPresent(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
json[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfPresent('id', id); |
|||
addIfPresent('alpha', alpha); |
|||
addIfPresent('anchor', _offsetToJson(anchor)); |
|||
addIfPresent('clickable', clickable); |
|||
addIfPresent('draggable', draggable); |
|||
addIfPresent('icon', icon.toMap()); |
|||
addIfPresent('infoWindowEnable', infoWindowEnable); |
|||
addIfPresent('infoWindow', infoWindow._toMap()); |
|||
addIfPresent('position', position.toJson()); |
|||
addIfPresent('rotation', rotation); |
|||
addIfPresent('visible', visible); |
|||
addIfPresent('zIndex', zIndex); |
|||
return json; |
|||
} |
|||
|
|||
dynamic _offsetToJson(Offset offset) { |
|||
return <dynamic>[offset.dx, offset.dy]; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is! Marker) return false; |
|||
final Marker typedOther = other; |
|||
return id == typedOther.id && |
|||
alpha == typedOther.alpha && |
|||
anchor == typedOther.anchor && |
|||
clickable == typedOther.clickable && |
|||
draggable == typedOther.draggable && |
|||
icon == typedOther.icon && |
|||
infoWindowEnable == typedOther.infoWindowEnable && |
|||
infoWindow == typedOther.infoWindow && |
|||
position == typedOther.position && |
|||
rotation == typedOther.rotation && |
|||
visible == typedOther.visible && |
|||
zIndex == typedOther.zIndex; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => super.hashCode; |
|||
|
|||
@override |
|||
String toString() { |
|||
return 'Marker{id: $id, alpha: $alpha, anchor: $anchor, ' |
|||
'clickable: $clickable, draggable: $draggable,' |
|||
'icon: $icon, infoWindowEnable: $infoWindowEnable, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' |
|||
'visible: $visible, zIndex: $zIndex, onTap: $onTap}'; |
|||
} |
|||
} |
|||
|
|||
Map<String, Marker> keyByMarkerId(Iterable<Marker> markers) { |
|||
return Map<String, Marker>.fromEntries( |
|||
markers.map( |
|||
(Marker marker) => MapEntry<String, Marker>(marker.id, marker.clone()), |
|||
), |
|||
); |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
import 'dart:ui' show hashValues; |
|||
|
|||
import 'package:flutter/foundation.dart' show setEquals; |
|||
|
|||
import 'types.dart'; |
|||
import 'marker.dart'; |
|||
|
|||
/// 用以描述Marker的更新项 |
|||
class MarkerUpdates { |
|||
/// 根据之前的marker列表[previous]和当前的marker列表[current]创建[MakerUpdates]. |
|||
MarkerUpdates.from(Set<Marker> previous, Set<Marker> current) { |
|||
// ignore: unnecessary_null_comparison |
|||
if (previous == null) { |
|||
previous = Set<Marker>.identity(); |
|||
} |
|||
|
|||
// ignore: unnecessary_null_comparison |
|||
if (current == null) { |
|||
current = Set<Marker>.identity(); |
|||
} |
|||
|
|||
final Map<String, Marker> previousMarkers = keyByMarkerId(previous); |
|||
final Map<String, Marker> currentMarkers = keyByMarkerId(current); |
|||
|
|||
final Set<String> prevMarkerIds = previousMarkers.keys.toSet(); |
|||
final Set<String> currentMarkerIds = currentMarkers.keys.toSet(); |
|||
|
|||
Marker idToCurrentMarker(String id) { |
|||
return currentMarkers[id]!; |
|||
} |
|||
|
|||
final Set<String> _markerIdsToRemove = prevMarkerIds.difference( |
|||
currentMarkerIds, |
|||
); |
|||
|
|||
final Set<Marker> _markersToAdd = |
|||
currentMarkerIds |
|||
.difference(prevMarkerIds) |
|||
.map(idToCurrentMarker) |
|||
.toSet(); |
|||
|
|||
bool hasChanged(Marker current) { |
|||
final Marker? previous = previousMarkers[current.id]; |
|||
return current != previous; |
|||
} |
|||
|
|||
final Set<Marker> _markersToChange = |
|||
currentMarkerIds |
|||
.intersection(prevMarkerIds) |
|||
.map(idToCurrentMarker) |
|||
.where(hasChanged) |
|||
.toSet(); |
|||
|
|||
markersToAdd = _markersToAdd; |
|||
markerIdsToRemove = _markerIdsToRemove; |
|||
markersToChange = _markersToChange; |
|||
} |
|||
|
|||
/// 想要添加的marker集合. |
|||
Set<Marker>? markersToAdd; |
|||
|
|||
/// 想要删除的marker的id集合 |
|||
Set<String>? markerIdsToRemove; |
|||
|
|||
/// 想要更新的marker集合. |
|||
Set<Marker>? markersToChange; |
|||
|
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> updateMap = <String, dynamic>{}; |
|||
|
|||
void addIfNonNull(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
updateMap[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfNonNull('markersToAdd', serializeOverlaySet(markersToAdd!)); |
|||
addIfNonNull('markersToChange', serializeOverlaySet(markersToChange!)); |
|||
addIfNonNull('markerIdsToRemove', markerIdsToRemove?.toList()); |
|||
|
|||
return updateMap; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is! MarkerUpdates) return false; |
|||
final MarkerUpdates typedOther = other; |
|||
return setEquals(markersToAdd, typedOther.markersToAdd) && |
|||
setEquals(markerIdsToRemove, typedOther.markerIdsToRemove) && |
|||
setEquals(markersToChange, typedOther.markersToChange); |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => |
|||
Object.hash(markersToAdd, markerIdsToRemove, markersToChange); |
|||
|
|||
@override |
|||
String toString() { |
|||
return '_MarkerUpdates{markersToAdd: $markersToAdd, ' |
|||
'markerIdsToRemove: $markerIdsToRemove, ' |
|||
'markersToChange: $markersToChange}'; |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
// Copyright 2019 The Chromium Authors. All rights reserved. |
|||
// Use of this source code is governed by a BSD-style license that can be |
|||
// found in the LICENSE file. |
|||
|
|||
import 'package:flutter/foundation.dart' show listEquals; |
|||
import 'package:flutter/material.dart' show Color; |
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'base_overlay.dart'; |
|||
import 'polyline.dart'; |
|||
|
|||
/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 |
|||
class Polygon extends BaseOverlay { |
|||
/// 默认构造函数 |
|||
Polygon( |
|||
{required this.points, |
|||
double strokeWidth = 10, |
|||
this.strokeColor = const Color(0xCC00BFFF), |
|||
this.fillColor = const Color(0xC487CEFA), |
|||
this.visible = true, |
|||
this.joinType = JoinType.bevel}) |
|||
: assert(points.length > 0), |
|||
this.strokeWidth = (strokeWidth <= 0 ? 10 : strokeWidth), |
|||
super(); |
|||
|
|||
/// 覆盖物的坐标点数组,不能为空 |
|||
final List<LatLng> points; |
|||
|
|||
/// 边框宽度,单位为逻辑像素,同Android中的dp,iOS中的point |
|||
final double strokeWidth; |
|||
|
|||
/// 边框颜色,默认值为(0xCCC4E0F0) |
|||
final Color strokeColor; |
|||
|
|||
/// 填充颜色,默认值为(0xC4E0F0CC) |
|||
final Color fillColor; |
|||
|
|||
/// 是否可见 |
|||
final bool visible; |
|||
|
|||
/// 连接点类型,该参数不支持copy时修改,仅能在初始化时设置一次 |
|||
final JoinType joinType; |
|||
|
|||
/// 实际copy函数 |
|||
Polygon copyWith({ |
|||
List<LatLng>? pointsParam, |
|||
double? strokeWidthParam, |
|||
Color? strokeColorParam, |
|||
Color? fillColorParam, |
|||
bool? visibleParam, |
|||
}) { |
|||
Polygon copyPolyline = Polygon( |
|||
points: pointsParam ?? points, |
|||
strokeWidth: strokeWidthParam ?? strokeWidth, |
|||
strokeColor: strokeColorParam ?? strokeColor, |
|||
fillColor: fillColorParam ?? fillColor, |
|||
visible: visibleParam ?? visible, |
|||
joinType: joinType, |
|||
); |
|||
copyPolyline.setIdForCopy(id); |
|||
return copyPolyline; |
|||
} |
|||
|
|||
Polygon clone() => copyWith(); |
|||
|
|||
/// 转换成可以序列化的map |
|||
@override |
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> json = <String, dynamic>{}; |
|||
|
|||
void addIfPresent(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
json[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfPresent('id', id); |
|||
json['points'] = _pointsToJson(); |
|||
addIfPresent('strokeWidth', strokeWidth); |
|||
addIfPresent('strokeColor', strokeColor.value); |
|||
addIfPresent('fillColor', fillColor.value); |
|||
addIfPresent('visible', visible); |
|||
addIfPresent('joinType', joinType.index); |
|||
return json; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is !Polygon) return false; |
|||
final Polygon typedOther = other; |
|||
return id == typedOther.id && |
|||
listEquals(points, typedOther.points) && |
|||
strokeWidth == typedOther.strokeWidth && |
|||
strokeColor == typedOther.strokeColor && |
|||
fillColor == typedOther.fillColor && |
|||
visible == typedOther.visible && |
|||
joinType == typedOther.joinType; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => super.hashCode; |
|||
|
|||
dynamic _pointsToJson() { |
|||
final List<dynamic> result = <dynamic>[]; |
|||
for (final LatLng point in points) { |
|||
result.add(point.toJson()); |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
Map<String, Polygon> keyByPolygonId(Iterable<Polygon> polylines) { |
|||
// ignore: unnecessary_null_comparison |
|||
if (polylines == null) { |
|||
return <String, Polygon>{}; |
|||
} |
|||
return Map<String, Polygon>.fromEntries(polylines.map((Polygon polyline) => |
|||
MapEntry<String, Polygon>(polyline.id, polyline.clone()))); |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright 2018 The Chromium Authors. All rights reserved. |
|||
// Use of this source code is governed by a BSD-style license that can be |
|||
// found in the LICENSE file. |
|||
|
|||
import 'dart:ui' show hashValues; |
|||
|
|||
import 'package:flutter/foundation.dart' show setEquals; |
|||
import 'types.dart'; |
|||
|
|||
/// 该类主要用以描述[Polygon]的增删改等更新操作 |
|||
class PolygonUpdates { |
|||
/// 通过Polygon的前后更新集合构造一个PolygonUpdates |
|||
PolygonUpdates.from(Set<Polygon> previous, Set<Polygon> current) { |
|||
// ignore: unnecessary_null_comparison |
|||
if (previous == null) { |
|||
previous = Set<Polygon>.identity(); |
|||
} |
|||
|
|||
// ignore: unnecessary_null_comparison |
|||
if (current == null) { |
|||
current = Set<Polygon>.identity(); |
|||
} |
|||
|
|||
final Map<String, Polygon> previousPolygons = keyByPolygonId(previous); |
|||
final Map<String, Polygon> currentPolygons = keyByPolygonId(current); |
|||
|
|||
final Set<String> prevPolygonIds = previousPolygons.keys.toSet(); |
|||
final Set<String> currentPolygonIds = currentPolygons.keys.toSet(); |
|||
|
|||
Polygon idToCurrentPolygon(String id) { |
|||
return currentPolygons[id]!; |
|||
} |
|||
|
|||
final Set<String> _polygonIdsToRemove = prevPolygonIds.difference( |
|||
currentPolygonIds, |
|||
); |
|||
|
|||
final Set<Polygon> _polygonsToAdd = |
|||
currentPolygonIds |
|||
.difference(prevPolygonIds) |
|||
.map(idToCurrentPolygon) |
|||
.toSet(); |
|||
|
|||
bool hasChanged(Polygon current) { |
|||
final Polygon previous = previousPolygons[current.id]!; |
|||
return current != previous; |
|||
} |
|||
|
|||
final Set<Polygon> _polygonsToChange = |
|||
currentPolygonIds |
|||
.intersection(prevPolygonIds) |
|||
.map(idToCurrentPolygon) |
|||
.where(hasChanged) |
|||
.toSet(); |
|||
|
|||
polygonsToAdd = _polygonsToAdd; |
|||
polygonIdsToRemove = _polygonIdsToRemove; |
|||
polygonsToChange = _polygonsToChange; |
|||
} |
|||
|
|||
/// 想要添加的polygon对象集合. |
|||
Set<Polygon>? polygonsToAdd; |
|||
|
|||
/// 想要删除的polygon的id集合 |
|||
Set<String>? polygonIdsToRemove; |
|||
|
|||
/// 想要更新的polygon对象集合 |
|||
Set<Polygon>? polygonsToChange; |
|||
|
|||
/// 转换成可以序列化的map |
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> updateMap = <String, dynamic>{}; |
|||
|
|||
void addIfNonNull(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
updateMap[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfNonNull('polygonsToAdd', serializeOverlaySet(polygonsToAdd!)); |
|||
addIfNonNull('polygonsToChange', serializeOverlaySet(polygonsToChange!)); |
|||
addIfNonNull('polygonIdsToRemove', polygonIdsToRemove?.toList()); |
|||
|
|||
return updateMap; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is! PolygonUpdates) return false; |
|||
final PolygonUpdates typedOther = other; |
|||
return setEquals(polygonsToAdd, typedOther.polygonsToAdd) && |
|||
setEquals(polygonIdsToRemove, typedOther.polygonIdsToRemove) && |
|||
setEquals(polygonsToChange, typedOther.polygonsToChange); |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => |
|||
Object.hash(polygonsToAdd, polygonIdsToRemove, polygonsToChange); |
|||
|
|||
@override |
|||
String toString() { |
|||
return '_PolygonUpdates{polygonsToAdd: $polygonsToAdd, ' |
|||
'polygonIdsToRemove: $polygonIdsToRemove, ' |
|||
'polygonsToChange: $polygonsToChange}'; |
|||
} |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
// Copyright 2019 The Chromium Authors. All rights reserved. |
|||
// Use of this source code is governed by a BSD-style license that can be |
|||
// found in the LICENSE file. |
|||
|
|||
import 'package:amap_flutter_map/src/types/bitmap.dart'; |
|||
import 'package:flutter/foundation.dart' show listEquals; |
|||
import 'package:flutter/material.dart' show Color; |
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'base_overlay.dart'; |
|||
|
|||
/// 虚线类型 |
|||
enum DashLineType { |
|||
/// 不画虚线 |
|||
none, |
|||
|
|||
/// 方块样式 |
|||
square, |
|||
|
|||
/// 圆点样式 |
|||
circle, |
|||
} |
|||
|
|||
/// 线头类型 |
|||
enum CapType { |
|||
/// 普通头 |
|||
butt, |
|||
|
|||
/// 扩展头 |
|||
square, |
|||
|
|||
/// 箭头 |
|||
arrow, |
|||
|
|||
/// 圆形头 |
|||
round, |
|||
} |
|||
|
|||
/// 连接点类型 |
|||
enum JoinType { |
|||
/// 斜面连接点 |
|||
bevel, |
|||
|
|||
/// 斜接连接点 |
|||
miter, |
|||
|
|||
/// 圆角连接点 |
|||
round, |
|||
} |
|||
|
|||
/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 |
|||
class Polyline extends BaseOverlay { |
|||
/// 默认构造函数 |
|||
Polyline({ |
|||
required this.points, |
|||
double width = 10, |
|||
this.visible = true, |
|||
this.geodesic = false, |
|||
double alpha = 1.0, |
|||
this.dashLineType = DashLineType.none, |
|||
this.capType = CapType.butt, |
|||
this.joinType = JoinType.bevel, |
|||
this.customTexture, |
|||
this.onTap, |
|||
this.color = const Color(0xCCC4E0F0), |
|||
}) : assert(points.length > 0), |
|||
this.width = (width <= 0 ? 10 : width), |
|||
this.alpha =(alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)), |
|||
super(); |
|||
|
|||
/// 覆盖物的坐标点数组,points不能为空 |
|||
final List<LatLng> points; |
|||
|
|||
/// 线宽,单位为逻辑像素,同Android中的dp,iOS中的point |
|||
final double width; |
|||
|
|||
/// 是否可见 |
|||
final bool visible; |
|||
|
|||
/// 透明度 |
|||
final double alpha; |
|||
|
|||
/// 覆盖物颜色,默认值为(0xCCC4E0F0). |
|||
final Color color; |
|||
|
|||
/// 自定义纹理图片,注意: 如果设置了自定义纹理图片,则color的设置将无效; |
|||
final BitmapDescriptor? customTexture; |
|||
|
|||
/// 是否为大地曲线 |
|||
final bool geodesic; |
|||
|
|||
/// 虚线类型 |
|||
final DashLineType dashLineType; |
|||
|
|||
/// 连接点类型 |
|||
final JoinType joinType; |
|||
|
|||
/// 线头类型 |
|||
final CapType capType; |
|||
|
|||
/// 点击回调(回调参数为id) |
|||
final ArgumentCallback<String>? onTap; |
|||
|
|||
/// 实际copy函数 |
|||
Polyline copyWith({ |
|||
List<LatLng>? pointsParam, |
|||
double? widthParam, |
|||
int? zIndexParam, |
|||
bool? visibleParam, |
|||
double? alphaParam, |
|||
DashLineType? dashLineTypeParam, |
|||
CapType? capTypeParam, |
|||
JoinType? joinTypeParam, |
|||
BitmapDescriptor? customTextureParam, |
|||
ArgumentCallback<String>? onTapParam, |
|||
Color? colorParam, |
|||
}) { |
|||
Polyline copyPolyline = Polyline( |
|||
points: pointsParam ?? points, |
|||
width: widthParam ?? width, |
|||
visible: visibleParam ?? visible, |
|||
geodesic: geodesic, |
|||
alpha: alphaParam ?? alpha, |
|||
dashLineType: dashLineTypeParam ?? dashLineType, |
|||
capType: capTypeParam ?? capType, |
|||
joinType: joinTypeParam ?? joinType, |
|||
customTexture: customTextureParam ?? customTexture, |
|||
onTap: onTapParam ?? onTap, |
|||
color: colorParam ?? color, |
|||
); |
|||
copyPolyline.setIdForCopy(id); |
|||
return copyPolyline; |
|||
} |
|||
|
|||
Polyline clone() => copyWith(); |
|||
|
|||
/// 将对象转换为可序列化的map. |
|||
@override |
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> json = <String, dynamic>{}; |
|||
|
|||
void addIfPresent(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
json[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfPresent('id', id); |
|||
json['points'] = _pointsToJson(); |
|||
addIfPresent('width', width); |
|||
addIfPresent('visible', visible); |
|||
addIfPresent('geodesic', geodesic); |
|||
addIfPresent('alpha', alpha); |
|||
addIfPresent('dashLineType', dashLineType.index); |
|||
addIfPresent('capType', capType.index); |
|||
addIfPresent('joinType', joinType.index); |
|||
addIfPresent('customTexture', customTexture?.toMap()); |
|||
addIfPresent('color', color.value); |
|||
return json; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is !Polyline) return false; |
|||
final Polyline typedOther = other; |
|||
return id == typedOther.id && |
|||
listEquals(points, typedOther.points) && |
|||
width == typedOther.width && |
|||
visible == typedOther.visible && |
|||
geodesic == typedOther.geodesic && |
|||
alpha == typedOther.alpha && |
|||
dashLineType == typedOther.dashLineType && |
|||
capType == typedOther.capType && |
|||
joinType == typedOther.joinType && |
|||
color == typedOther.color; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => super.hashCode; |
|||
|
|||
dynamic _pointsToJson() { |
|||
final List<dynamic> result = <dynamic>[]; |
|||
for (final LatLng point in points) { |
|||
result.add(point.toJson()); |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
Map<String, Polyline> keyByPolylineId(Iterable<Polyline> polylines) { |
|||
// ignore: unnecessary_null_comparison |
|||
if (polylines == null) { |
|||
return <String, Polyline>{}; |
|||
} |
|||
return Map<String, Polyline>.fromEntries(polylines.map((Polyline polyline) => |
|||
MapEntry<String, Polyline>(polyline.id, polyline.clone()))); |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright 2018 The Chromium Authors. All rights reserved. |
|||
// Use of this source code is governed by a BSD-style license that can be |
|||
// found in the LICENSE file. |
|||
|
|||
import 'package:flutter/foundation.dart' show setEquals; |
|||
|
|||
import 'polyline.dart'; |
|||
import 'types.dart'; |
|||
|
|||
/// 该类主要用以描述[Polyline]的增删改等更新操作 |
|||
class PolylineUpdates { |
|||
/// 通过polyline的前后更新集合构造一个polylineUpdates |
|||
PolylineUpdates.from(Set<Polyline> previous, Set<Polyline> current) { |
|||
// ignore: unnecessary_null_comparison |
|||
if (previous == null) { |
|||
previous = Set<Polyline>.identity(); |
|||
} |
|||
|
|||
// ignore: unnecessary_null_comparison |
|||
if (current == null) { |
|||
current = Set<Polyline>.identity(); |
|||
} |
|||
|
|||
final Map<String, Polyline> previousPolylines = keyByPolylineId(previous); |
|||
final Map<String, Polyline> currentPolylines = keyByPolylineId(current); |
|||
|
|||
final Set<String> prevPolylineIds = previousPolylines.keys.toSet(); |
|||
final Set<String> currentPolylineIds = currentPolylines.keys.toSet(); |
|||
|
|||
Polyline idToCurrentPolyline(String id) { |
|||
return currentPolylines[id]!; |
|||
} |
|||
|
|||
final Set<String> _polylineIdsToRemove = prevPolylineIds.difference( |
|||
currentPolylineIds, |
|||
); |
|||
|
|||
final Set<Polyline> _polylinesToAdd = |
|||
currentPolylineIds |
|||
.difference(prevPolylineIds) |
|||
.map(idToCurrentPolyline) |
|||
.toSet(); |
|||
|
|||
bool hasChanged(Polyline current) { |
|||
final Polyline previous = previousPolylines[current.id]!; |
|||
return current != previous; |
|||
} |
|||
|
|||
final Set<Polyline> _polylinesToChange = |
|||
currentPolylineIds |
|||
.intersection(prevPolylineIds) |
|||
.map(idToCurrentPolyline) |
|||
.where(hasChanged) |
|||
.toSet(); |
|||
|
|||
polylinesToAdd = _polylinesToAdd; |
|||
polylineIdsToRemove = _polylineIdsToRemove; |
|||
polylinesToChange = _polylinesToChange; |
|||
} |
|||
|
|||
/// 用于添加polyline的集合 |
|||
Set<Polyline>? polylinesToAdd; |
|||
|
|||
/// 需要删除的plyline的id集合 |
|||
Set<String>? polylineIdsToRemove; |
|||
|
|||
/// 用于更新polyline的集合 |
|||
Set<Polyline>? polylinesToChange; |
|||
|
|||
/// 将对象装换为可序列化的对象 |
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> updateMap = <String, dynamic>{}; |
|||
|
|||
void addIfNonNull(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
updateMap[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfNonNull('polylinesToAdd', serializeOverlaySet(polylinesToAdd!)); |
|||
addIfNonNull('polylinesToChange', serializeOverlaySet(polylinesToChange!)); |
|||
addIfNonNull('polylineIdsToRemove', polylineIdsToRemove?.toList()); |
|||
|
|||
return updateMap; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (other.runtimeType != runtimeType) return false; |
|||
if (other is! PolylineUpdates) return false; |
|||
final PolylineUpdates typedOther = other; |
|||
return setEquals(polylinesToAdd, typedOther.polylinesToAdd) && |
|||
setEquals(polylineIdsToRemove, typedOther.polylineIdsToRemove) && |
|||
setEquals(polylinesToChange, typedOther.polylinesToChange); |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => |
|||
Object.hash(polylinesToAdd, polylineIdsToRemove, polylinesToChange); |
|||
|
|||
@override |
|||
String toString() { |
|||
return '_PolylineUpdates{polylinesToAdd: $polylinesToAdd, ' |
|||
'polylineIdsToRemove: $polylineIdsToRemove, ' |
|||
'polylinesToChange: $polylinesToChange}'; |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
export 'camera.dart'; |
|||
export 'ui.dart'; |
|||
export 'base_overlay.dart'; |
|||
export 'marker.dart'; |
|||
export 'marker_updates.dart'; |
|||
export 'polyline.dart'; |
|||
export 'polyline_updates.dart'; |
|||
export 'polygon.dart'; |
|||
export 'polygon_updates.dart'; |
|||
export 'camera.dart'; |
|||
export 'bitmap.dart'; |
|||
@ -0,0 +1,257 @@ |
|||
import 'dart:typed_data'; |
|||
import 'dart:ui' show Color; |
|||
import 'package:amap_flutter_base/amap_flutter_base.dart'; |
|||
import 'package:amap_flutter_map/amap_flutter_map.dart'; |
|||
|
|||
/// 地图类型 |
|||
enum MapType { |
|||
/// 普通地图 |
|||
normal, |
|||
|
|||
/// 卫星地图 |
|||
satellite, |
|||
|
|||
/// 夜间视图 |
|||
night, |
|||
|
|||
/// 导航视图 |
|||
navi, |
|||
|
|||
/// 公交视图 |
|||
bus, |
|||
} |
|||
|
|||
// 设置摄像机的边界. |
|||
class CameraTargetBounds { |
|||
/// 使用指定的边界框或空值创建摄影机目标边界 |
|||
/// |
|||
/// 设置为null时代表不指定边界 |
|||
const CameraTargetBounds(this.bounds); |
|||
|
|||
/// 摄像机的边界. |
|||
/// |
|||
/// null代表不指定边界 |
|||
final LatLngBounds? bounds; |
|||
|
|||
/// 取消指定边界 |
|||
static const CameraTargetBounds unbounded = CameraTargetBounds(null); |
|||
|
|||
/// 转换成json对象 |
|||
dynamic toJson() => <dynamic>[bounds?.toJson()]; |
|||
|
|||
@override |
|||
bool operator ==(dynamic other) { |
|||
if (identical(this, other)) return true; |
|||
if (runtimeType != other.runtimeType) return false; |
|||
final CameraTargetBounds typedOther = other; |
|||
return bounds == typedOther.bounds; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => bounds.hashCode; |
|||
|
|||
@override |
|||
String toString() { |
|||
return 'CameraTargetBounds(bounds: $bounds)'; |
|||
} |
|||
} |
|||
|
|||
/// 地图最大最小缩放级别的封装对象 |
|||
class MinMaxZoomPreference { |
|||
/// 为地图创建一个不可变的最大最小缩放范围 |
|||
/// |
|||
/// 缩放级别范围为[3, 20],超出范围取边界值 |
|||
/// |
|||
const MinMaxZoomPreference(double minZoom, double maxZoom) |
|||
: this.minZoom = |
|||
((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) |
|||
? maxZoom |
|||
: minZoom), |
|||
this.maxZoom = |
|||
((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) |
|||
? minZoom |
|||
: maxZoom); |
|||
|
|||
/// 最小zoomLevel |
|||
final double? minZoom; |
|||
|
|||
/// 最大zoomLevel |
|||
final double? maxZoom; |
|||
|
|||
/// 高德地图默认zoomLevel的范围. |
|||
static const MinMaxZoomPreference defaultPreference = MinMaxZoomPreference( |
|||
3, |
|||
20, |
|||
); |
|||
|
|||
/// JSON序列化. |
|||
dynamic toJson() => <dynamic>[minZoom, maxZoom]; |
|||
|
|||
@override |
|||
bool operator ==(dynamic other) { |
|||
if (identical(this, other)) return true; |
|||
if (runtimeType != other.runtimeType) return false; |
|||
final MinMaxZoomPreference typedOther = other; |
|||
return minZoom == typedOther.minZoom && maxZoom == typedOther.maxZoom; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => Object.hash(minZoom, maxZoom); |
|||
|
|||
@override |
|||
String toString() { |
|||
return 'MinMaxZoomPreference(minZoom: $minZoom, maxZoom: $maxZoom)'; |
|||
} |
|||
} |
|||
|
|||
///定位小蓝点配置项 |
|||
class MyLocationStyleOptions { |
|||
///是否显示定位小蓝点 |
|||
bool enabled; |
|||
|
|||
///精度圈填充色 |
|||
Color? circleFillColor; |
|||
|
|||
///精度圈边框色 |
|||
Color? circleStrokeColor; |
|||
|
|||
///精度圈边框宽度 |
|||
double? circleStrokeWidth; |
|||
|
|||
///小蓝点图标 |
|||
BitmapDescriptor? icon; |
|||
|
|||
MyLocationStyleOptions( |
|||
this.enabled, { |
|||
this.circleFillColor, |
|||
this.circleStrokeColor, |
|||
this.circleStrokeWidth, |
|||
this.icon, |
|||
}); |
|||
|
|||
MyLocationStyleOptions clone() { |
|||
return MyLocationStyleOptions( |
|||
enabled, |
|||
circleFillColor: circleFillColor, |
|||
circleStrokeColor: circleStrokeColor, |
|||
circleStrokeWidth: circleStrokeWidth, |
|||
icon: icon, |
|||
); |
|||
} |
|||
|
|||
static MyLocationStyleOptions? fromMap(dynamic json) { |
|||
if (null == json) { |
|||
return null; |
|||
} |
|||
return MyLocationStyleOptions( |
|||
json['enabled'] ?? false, |
|||
circleFillColor: json['circleFillColor'] ?? null, |
|||
circleStrokeColor: json['circleStrokeColor'] ?? null, |
|||
circleStrokeWidth: json['circleStrokeWidth'] ?? null, |
|||
icon: json['icon'] ?? null, |
|||
); |
|||
} |
|||
|
|||
Map<String, dynamic> toMap() { |
|||
final Map<String, dynamic> json = <String, dynamic>{}; |
|||
|
|||
void addIfPresent(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
json[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfPresent('enabled', enabled); |
|||
addIfPresent('circleFillColor', circleFillColor?.value); |
|||
addIfPresent('circleStrokeColor', circleStrokeColor?.value); |
|||
addIfPresent('circleStrokeWidth', circleStrokeWidth); |
|||
addIfPresent('icon', icon?.toMap()); |
|||
return json; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (runtimeType != other.runtimeType) return false; |
|||
if (other is! MyLocationStyleOptions) return false; |
|||
final MyLocationStyleOptions typedOther = other; |
|||
return enabled == typedOther.enabled && |
|||
circleFillColor == typedOther.circleFillColor && |
|||
circleStrokeColor == typedOther.circleStrokeColor && |
|||
icon == typedOther.icon; |
|||
} |
|||
|
|||
@override |
|||
String toString() { |
|||
return 'MyLocationOptionsStyle{' |
|||
'enabled: $enabled,' |
|||
'circleFillColor: $circleFillColor,' |
|||
'circleStrokeColor: $circleStrokeColor,' |
|||
'icon: $icon, }'; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => |
|||
Object.hash(enabled, circleFillColor, circleStrokeColor, icon); |
|||
} |
|||
|
|||
///地图自定义样式 |
|||
class CustomStyleOptions { |
|||
///开关项,是否开启自定义地图 |
|||
bool enabled; |
|||
|
|||
///自定义样式的二进制数据,对应下载的自定义地图文件中的style.data中的二进制数据 |
|||
Uint8List? styleData; |
|||
|
|||
///自定义扩展样式的二进制数据,对应下载的自定义地图文件中的style_extra.data中的二进制数据 |
|||
Uint8List? styleExtraData; |
|||
|
|||
CustomStyleOptions(this.enabled, {this.styleData, this.styleExtraData}); |
|||
|
|||
static CustomStyleOptions? fromMap(dynamic json) { |
|||
if (json == null) { |
|||
return null; |
|||
} |
|||
return CustomStyleOptions( |
|||
json['enabled'] ?? false, |
|||
styleData: json['styleData'] ?? null, |
|||
styleExtraData: json['styleExtraData'] ?? null, |
|||
); |
|||
} |
|||
|
|||
dynamic toMap() { |
|||
final Map<String, dynamic> json = <String, dynamic>{}; |
|||
void addIfPresent(String fieldName, dynamic value) { |
|||
if (value != null) { |
|||
json[fieldName] = value; |
|||
} |
|||
} |
|||
|
|||
addIfPresent('enabled', enabled); |
|||
addIfPresent('styleData', styleData); |
|||
addIfPresent('styleExtraData', styleExtraData); |
|||
return json; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(Object other) { |
|||
if (identical(this, other)) return true; |
|||
if (runtimeType != other.runtimeType) return false; |
|||
if (other is! CustomStyleOptions) return false; |
|||
final CustomStyleOptions typedOther = other; |
|||
return enabled == typedOther.enabled && |
|||
styleData == typedOther.styleData && |
|||
styleExtraData == typedOther.styleExtraData; |
|||
} |
|||
|
|||
@override |
|||
int get hashCode => Object.hash(enabled, styleData, styleExtraData); |
|||
|
|||
CustomStyleOptions clone() { |
|||
return CustomStyleOptions( |
|||
enabled, |
|||
styleData: styleData, |
|||
styleExtraData: styleExtraData, |
|||
); |
|||
} |
|||
} |
|||
Binary file not shown.
@ -1,2 +1,2 @@ |
|||
#Sun May 11 13:39:53 IST 2025 |
|||
gradle.version=8.10 |
|||
#Tue May 20 09:53:38 CST 2025 |
|||
gradle.version=8.9 |
|||
|
|||
Loading…
Reference in new issue