release: 1.0.10

This commit is contained in:
Kuloud
2024-08-25 22:35:31 +08:00
parent 9c1509c59d
commit 2a889ebbdf
19 changed files with 172 additions and 530 deletions

View File

@ -55,43 +55,37 @@ class AMapController {
_methodChannel
.onCameraMove(mapId: mapId)
.listen((CameraPositionMoveEvent e) {
_mapState._extensions.values.forEach((ext) => ext.onCameraMove(e.value));
_mapState.widget.onCameraMove?.call(e.value);
if (_mapState.widget.infoWindowAdapter != null) {
_mapState.updateMarkers();
}
});
_methodChannel
.onCameraMoveEnd(mapId: mapId)
.listen((CameraPositionMoveEndEvent e) {
_mapState._extensions.values
.forEach((ext) => ext.onCameraMoveEnd(e.value));
_mapState.widget.onCameraMoveEnd?.call(e.value);
});
_methodChannel
.onMapTap(mapId: mapId)
.listen(((MapTapEvent e) => _mapState.widget.onTap?.call(e.value)));
_methodChannel.onMapLongPress(mapId: mapId).listen(((MapLongPressEvent e) {
_mapState._extensions.values.forEach((ext) => ext.onLongPress(e.value));
_mapState.widget.onLongPress?.call(e.value);
}));
_methodChannel.onPoiTouched(mapId: mapId).listen(((MapPoiTouchEvent e) {
_mapState._extensions.values.forEach((ext) => ext.onPoiTouched(e.value));
_mapState.widget.onPoiTouched?.call(e.value);
}));
_methodChannel.onMarkerTap(mapId: mapId).listen((MarkerTapEvent e) {
_mapState._extensions.values.forEach((ext) => ext.onMarkerTap(e.value));
_mapState.onMarkerTap(e.value);
});
_methodChannel.onMarkerDragEnd(mapId: mapId).listen((MarkerDragEndEvent e) {
_mapState._extensions.values
.forEach((ext) => ext.onMarkerDragEnd(e.value));
_mapState.onMarkerDragEnd(e.value, e.position);
});
_methodChannel.onPolylineTap(mapId: mapId).listen((PolylineTapEvent e) {
_mapState._extensions.values.forEach((ext) => ext.onPolylineTap(e.value));
_mapState.onPolylineTap(e.value);
});
}
@ -150,4 +144,12 @@ class AMapController {
Future<LatLng> fromScreenCoordinate(ScreenCoordinate screenCoordinate) {
return _methodChannel.fromScreenLocation(screenCoordinate, mapId: mapId);
}
Future<String> getMapContentApprovalNumber() {
return _methodChannel.getMapContentApprovalNumber(mapId: mapId);
}
Future<String> getSatelliteImageApprovalNumber() {
return _methodChannel.getSatelliteImageApprovalNumber(mapId: mapId);
}
}

View File

@ -1,94 +0,0 @@
// Copyright 2023-2024 kuloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
part of amap_map;
/// 加载器,在[AMapWidget]的生命周期上绑定拓展
class AMapLoader extends StatefulWidget {
const AMapLoader(
{super.key, required this.mapView, required this.extensions});
final Widget mapView;
final List<AMapExtension> extensions;
@override
State<AMapLoader> createState() => _AMapLoaderState();
static void prepare() {
// TODO Loader 初始化静态方法在initState时做些逻辑
}
/// 传递构建视图,拓展按需进行包装和挂载定制视图
Widget buildFromExtension(AMapContext aMapContext) {
Widget child = mapView;
for (var e in extensions) {
child = e.build(aMapContext, child);
}
return child;
}
/// [didChangeDependencies] 状态更新时,挨个遍历进行拓展的更新操作
void prepareFromExtension(AMapContext aMapContext) {
for (var e in extensions) {
e.prepare(aMapContext);
}
}
/// [didChangeDependencies] 状态更新时,挨个遍历进行拓展的更新操作
void updateFromExtension(AMapContext aMapContext) {
for (var e in extensions) {
e.update(aMapContext);
}
// Future.forEach(extensions, (ext) => ext.update(aMapContext));
}
}
class _AMapLoaderState extends State<AMapLoader> {
@override
void didChangeDependencies() {
final aMapContext = AMapContext(
buildContext: context,
currentStep: CurrentStep.preparing,
loader: widget);
widget.prepareFromExtension(aMapContext);
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant AMapLoader oldWidget) {
final aMapContext = AMapContext(
buildContext: context,
currentStep: CurrentStep.updating,
loader: widget);
widget.updateFromExtension(aMapContext);
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
for (var e in widget.extensions) {
e.onDispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
// Set the extension context for this node.
final aMapContext = AMapContext(
buildContext: context,
currentStep: CurrentStep.building,
loader: widget);
return widget.buildFromExtension(aMapContext);
}
}

View File

@ -106,11 +106,11 @@ class AMapWidget extends StatefulWidget {
///需要应用到地图上的手势集合
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
final List<AMapExtension> extensions;
/// 设置地图语言
final MapLanguage? mapLanguage;
final InfoWindowAdapter? infoWindowAdapter;
/// 创建一个展示高德地图的widget
///
/// 在app首次启动时必须传入高德合规声明配置[privacyStatement],后续如果没有变化不需要重复设置
@ -152,8 +152,8 @@ class AMapWidget extends StatefulWidget {
this.markers = const <Marker>{},
this.polylines = const <Polyline>{},
this.polygons = const <Polygon>{},
this.extensions = const [],
this.mapLanguage,
this.infoWindowAdapter,
this.logoPosition,
this.logoBottomMargin,
this.logoLeftMargin})
@ -168,7 +168,7 @@ class _MapState extends State<AMapWidget> {
Map<String, Marker> _markers = <String, Marker>{};
Map<String, Polyline> _polylines = <String, Polyline>{};
Map<String, Polygon> _polygons = <String, Polygon>{};
Map<String, AMapExtension> _extensions = <String, AMapExtension>{};
Map<String, Widget?> _infoWindows = <String, Widget?>{};
final Completer<AMapController> _controller = Completer<AMapController>();
late _AMapOptions _mapOptions;
@ -190,22 +190,19 @@ class _MapState extends State<AMapWidget> {
onPlatformViewCreated,
);
return AMapLoader(
mapView: mapView,
extensions: widget.extensions,
return Stack(
children: [mapView, ..._infoWindows.values.nonNulls],
);
}
@override
void initState() {
AMapLoader.prepare();
super.initState();
_mapOptions = _AMapOptions.fromWidget(widget);
_markers = keyByMarkerId(widget.markers);
_polygons = keyByPolygonId(widget.polygons);
_polylines = keyByPolylineId(widget.polylines);
_extensions = keyByExtensionId(widget.extensions);
print('initState AMapWidget');
}
@ -233,10 +230,9 @@ class _MapState extends State<AMapWidget> {
void didUpdateWidget(covariant AMapWidget oldWidget) {
super.didUpdateWidget(oldWidget);
_updateOptions();
_updateMarkers();
updateMarkers();
_updatePolylines();
_updatePolygons();
_updateExtensions();
}
Future<void> onPlatformViewCreated(int id) async {
@ -247,14 +243,6 @@ class _MapState extends State<AMapWidget> {
);
_controller.complete(controller);
if (_extensions.isNotEmpty) {
debugPrint('[onPlatformViewCreated] $controller');
await Future.forEach(_extensions.values, (e) {
e.bindMethodChannel(controller.channel);
e.bindMapController(controller);
});
}
final MapCreatedCallback? _onMapCreated = widget.onMapCreated;
if (_onMapCreated != null) {
_onMapCreated(controller);
@ -303,12 +291,24 @@ class _MapState extends State<AMapWidget> {
_mapOptions = newOptions;
}
void _updateMarkers() async {
void updateMarkers() async {
final AMapController controller = await _controller.future;
MarkerUpdates markerUpdates =
MarkerUpdates.from(_markers.values.toSet(), widget.markers);
markerUpdates.markerIdsToRemove?.forEach((markerId) {
_removeInfoWindow(markerId);
});
// ignore: unawaited_futures
controller._updateMarkers(
MarkerUpdates.from(_markers.values.toSet(), widget.markers));
controller._updateMarkers(markerUpdates);
_markers = keyByMarkerId(widget.markers);
if (widget.infoWindowAdapter != null) {
_markers.values.forEach((marker) {
_onInfoWindowUpdate(marker);
});
}
}
void _updatePolylines() async {
@ -325,9 +325,19 @@ class _MapState extends State<AMapWidget> {
_polygons = keyByPolygonId(widget.polygons);
}
void _updateExtensions() async {
// final AMapController controller = await _controller.future;
_extensions = keyByExtensionId(widget.extensions);
void _onInfoWindowUpdate(Marker marker) {
if (widget.infoWindowAdapter != null) {
setState(() {
_infoWindows[marker.id] =
widget.infoWindowAdapter!.getInfoWindow(context, marker);
});
}
}
void _removeInfoWindow(String markerId) {
setState(() {
_infoWindows.remove(markerId);
});
}
}

View File

@ -284,4 +284,20 @@ class MethodChannelAMapFlutterMap implements AMapFlutterPlatform {
'map#fromScreenCoordinate', screenCoordinate.toJson());
return LatLng(latLng![0] as double, latLng[1] as double);
}
Future<String> getMapContentApprovalNumber({
required int mapId,
}) async {
return await channel(mapId)
.invokeMethod<String>('map#contentApprovalNumber') ??
'';
}
Future<String> getSatelliteImageApprovalNumber({
required int mapId,
}) async {
return await channel(mapId)
.invokeMethod<String>('map#satelliteImageApprovalNumber') ??
'';
}
}

View File

@ -1,135 +0,0 @@
import 'dart:async';
import 'package:amap_map/amap_map.dart';
import 'package:flutter/widgets.dart';
import 'package:x_amap_base/x_amap_base.dart';
/// [amap_map]插件支持默认的[InfoWindow]展示[title]和[snippet]字段,如果要自定义
/// [InfoWindow],需要集成此内置插件。
///
/// ```dart
/// AMapWidget map = AMapWidget(
/// onMapCreated: _onMapCreated,
/// markers: Set<Marker>.of(_markers.values),
/// extensions: [
/// InfoWindowExtension(
/// infoWindow: Container(
/// child: Text('Custom widget'),
/// ))
/// ],
/// )
/// ```
///
/// 此插件会在[AMapWidget]外侧套一层[Stack],根据屏幕坐标和经纬度进行适配自定义视图
///
/// ```dart
/// Stack(
/// children: [
/// child,
/// Positioned(
/// top: 0,
/// left: 0,
/// child: Visibility(child: infoWindow),
/// ),
/// ],
/// )
/// ```
class InfoWindowExtension extends AMapExtension {
InfoWindowExtension({required this.infoWindow, this.option});
final _streamController = StreamController<InfoWindowExtension>.broadcast();
final Widget infoWindow;
InfoWindowOption? option;
final GlobalKey _infoWindowKey = GlobalKey();
AMapController? mapController;
double? _x;
double? _y;
@override
void update(AMapContext aMapContext) {
_update();
}
@override
onCameraMove(CameraPosition value) {
_update();
}
_update() async {
if (mapController == null || option?.latLng == null) {
return;
}
final coordinate = await mapController!.toScreenCoordinate(option!.latLng!);
_x = coordinate.x.toDouble();
_y = coordinate.y.toDouble();
final size = _infoWindowKey.currentContext?.size;
if (size != null && _x != null && _y != null) {
_x = _x! - size.width / 2;
_y = _y! - size.height;
}
_streamController.add(this);
}
@override
bindMapController(AMapController controller) {
debugPrint('[InfoWindowExtension] bindMapController: $controller');
mapController = controller;
}
@override
Widget build(AMapContext aMapContext, Widget child) {
return Stack(
key: ValueKey(this),
children: [
child,
StreamBuilder<InfoWindowExtension>(
initialData: this,
stream: _streamController.stream,
builder: (context, snapshot) {
final data = snapshot.data;
if (data == null ||
!(data.option?.show ?? false) ||
data._x == null) {
return Container();
}
return Positioned(
top: data._y,
left: data._x,
child: Container(
key: _infoWindowKey,
margin: data.option?.offset,
child: infoWindow,
),
);
}),
],
);
}
@override
void onDispose() {
_streamController.close();
super.onDispose();
}
}
class InfoWindowOption {
bool show = false;
LatLng? latLng;
EdgeInsetsGeometry? offset;
InfoWindowOption({this.show = false, this.latLng, this.offset});
@override
String toString() {
return 'InfoWindowOption{show: $show, latLng: $latLng, offset: $offset}';
}
}

View File

@ -1 +0,0 @@
export 'buildin/info_window_extension.dart';

View File

@ -1,81 +0,0 @@
// Copyright 2023-2024 kuloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
import 'package:amap_map/amap_map.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:x_amap_base/x_amap_base.dart';
/// 插件拓展,绑定[AMapWidget]的生命周期和控制器,实现基于地图的拓展能力(通常是地图交互的可
/// 选功能)
abstract class AMapExtension {
late String _id;
String get id => _id;
AMapExtension() {
this._id = this.hashCode.toString();
}
/// 视图层级变化
/// since v1.0.4
void prepare(AMapContext aMapContext) {}
/// 视图变化
/// since v1.0.4
void update(AMapContext aMapContext) {}
void onDispose() {}
@Deprecated('使用 bindMapController 计划在1.0.6移除')
bindMethodChannel(MethodChannel channel) {}
/// 注入[AMapController] 方便地图交互
bindMapController(AMapController controller) {}
Widget build(AMapContext aMapContext, Widget child) {
/// 默认传递[AMapWidget],有部分场景需要在[AMapWidget]外包装容器,根据传递拓展列表顺序
/// 链式执行
return child;
}
/// since v1.0.4
onCameraMove(CameraPosition cameraPosition) {}
/// since v1.0.4
onCameraMoveEnd(CameraPosition cameraPosition) {}
/// since v1.0.4
onLongPress(LatLng latLng) {}
/// since v1.0.4
onPoiTouched(AMapPoi poi) {}
/// since v1.0.4
onMarkerTap(String value) {}
/// since v1.0.4
onMarkerDragEnd(String value) {}
/// since v1.0.4
onPolylineTap(String value) {}
}
Map<String, AMapExtension> keyByExtensionId(
Iterable<AMapExtension> extensions) {
// ignore: unnecessary_null_comparison
if (extensions == null) {
return <String, AMapExtension>{};
}
return Map<String, AMapExtension>.fromEntries(extensions.map(
(AMapExtension extension) =>
MapEntry<String, AMapExtension>(extension.id, extension)));
}

View File

@ -1,31 +0,0 @@
// Copyright 2023-2024 kuloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
import 'package:amap_map/amap_map.dart';
import 'package:flutter/material.dart';
/// 上下文
class AMapContext {
final BuildContext buildContext;
final CurrentStep currentStep;
final AMapLoader loader;
AMapContext(
{required this.buildContext,
required this.currentStep,
required this.loader});
}
enum CurrentStep {
preparing,
updating,
building,
}

View File

@ -0,0 +1,37 @@
import 'package:amap_map/amap_map.dart';
import 'package:flutter/widgets.dart';
abstract class InfoWindowAdapter {
Widget? getInfoWindow(BuildContext context, Marker marker);
}
abstract class BaseInfoWindowAdapter implements InfoWindowAdapter {
final AMapController? controller;
BaseInfoWindowAdapter(this.controller);
@override
Widget? getInfoWindow(BuildContext context, Marker marker) {
final contentView = buildInfoWindowContent(context, marker);
return (contentView != null)
? FutureBuilder(
future: controller?.toScreenCoordinate(marker.position),
builder: (context, snapshot) {
if (snapshot.hasData) {
double devicePixelRatio =
MediaQuery.of(context).devicePixelRatio;
return Positioned(
left: snapshot.data!.x.toDouble() / devicePixelRatio,
top: snapshot.data!.y.toDouble() / devicePixelRatio,
child: contentView,
);
} else {
return Container(); // 当未获取到数据时,返回空的 `Container`
}
},
)
: null;
}
Widget? buildInfoWindowContent(BuildContext context, Marker marker);
}

View File

@ -20,8 +20,5 @@ export 'polyline_updates.dart';
export 'polygon.dart';
export 'polygon_updates.dart';
export 'bitmap.dart';
export 'extension_context.dart';
export 'amap_extension.dart';
export 'screen_coordinate.dart';
export '../extension/extensions.dart';
export 'info_window_adapter.dart';