map.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'dart:math';
  4. import 'dart:typed_data';
  5. import 'dart:ui' as ui;
  6. import 'package:amap_flutter_base/amap_flutter_base.dart';
  7. import 'package:amap_flutter_map/amap_flutter_map.dart';
  8. import 'package:flutter/foundation.dart';
  9. import 'package:flutter/gestures.dart';
  10. import 'package:flutter/material.dart';
  11. import 'package:flutter/services.dart';
  12. import 'package:path_provider/path_provider.dart';
  13. import 'package:sport/pages/run/location.dart';
  14. import 'package:sport/pages/run/run_start.dart';
  15. import 'package:sport/pages/run/setting_page.dart';
  16. import 'package:sport/utils/path_smooth_tool.dart';
  17. import 'package:sport/utils/sport_utils.dart';
  18. import 'package:sport/widgets/dialog/request_dialog.dart';
  19. Future<File> getFile({String suffix = "png"}) async {
  20. String sTempDir = (await getTemporaryDirectory()).path;
  21. bool isDirExist = await Directory(sTempDir).exists();
  22. if (!isDirExist) {
  23. Directory(sTempDir).create();
  24. }
  25. var now = DateTime.now();
  26. File file = File(sTempDir + "/poster-temp-${now.millisecondsSinceEpoch}-${Random().nextInt(100)}.$suffix");
  27. return file;
  28. }
  29. Future<File> takeAMapSnapshot(AMapController? mapController) async {
  30. File file = await getFile();
  31. if (mapController != null) {
  32. var mapData = await mapController.takeSnapshot();
  33. if(mapData != null)
  34. file.writeAsBytesSync(mapData);
  35. }
  36. return file;
  37. }
  38. Future<Uint8List?> takeAMapSnapshotData(AMapController? mapController) async {
  39. if (mapController != null) {
  40. return await mapController.takeSnapshot();
  41. }
  42. return null;
  43. }
  44. class MapWidget extends StatefulWidget {
  45. final double distance;
  46. final int runMapType;
  47. final bool runMapKm;
  48. final List<Location> points;
  49. final Map<int, Location> pointsKm;
  50. final bool record;
  51. final bool anim;
  52. final int showType;
  53. const MapWidget({Key? key, this.showType = 0, this.record = false, this.anim = false, this.distance = 0, this.runMapType = 0, this.runMapKm = true, required this.points, required this.pointsKm}) : super(key: key);
  54. @override
  55. State<StatefulWidget> createState() => MapState();
  56. }
  57. class MapState extends State<MapWidget> with RunSetting, TickerProviderStateMixin {
  58. AMapController? _mapController;
  59. final Map<int, Marker> _markerMap = <int, Marker>{};
  60. final Map<int, Marker> _markerMapReady = <int, Marker>{};
  61. AnimationController? _animationController;
  62. Animation? _animation;
  63. Marker? _marker;
  64. late double _avgSpeed;
  65. int _perKm = 0;
  66. List<Location> polylines = [];
  67. bool _mapReady = false;
  68. double _zoom = 15;
  69. @override
  70. void initState() {
  71. super.initState();
  72. List<Location> points = widget.points;
  73. _avgSpeed = points.map((e) => e.speed ?? 0).reduce((value, element) => value + element) / points.length;
  74. runMapType = widget.runMapType;
  75. loadCustomData();
  76. }
  77. @override
  78. void dispose() {
  79. _animationController?.dispose();
  80. _mapController?.disponse();
  81. _mapController = null;
  82. super.dispose();
  83. }
  84. @override
  85. void didUpdateWidget(covariant MapWidget oldWidget) {
  86. super.didUpdateWidget(oldWidget);
  87. if (oldWidget.runMapKm != widget.runMapKm) {
  88. _fillMarker(widget.points).then((value) => setState(() {
  89. _markerMap.addAll(_markerMapReady);
  90. _markerMap.remove(0);
  91. }));
  92. }
  93. if (oldWidget.runMapType != widget.runMapType) {
  94. runMapType = widget.runMapType;
  95. loadCustomData();
  96. }
  97. }
  98. void _onMapCreated(AMapController controller) async {
  99. _mapController = controller;
  100. if(widget.anim != true){
  101. initAnimation();
  102. }
  103. }
  104. initAnimation() async{
  105. List<Location> points = widget.points;
  106. await _fillMarker(points);
  107. if (widget.anim == true) {
  108. if (widget.showType == 0) {
  109. _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(tilt: 45.0, zoom: 17, bearing: -15, target: LatLng(points.first.latitude, points.first.longitude))), animated: true, duration: 1000);
  110. } else {
  111. LatLngBounds? bounds = LatLngBoundsBuilder.fromList(points.map((e) => LatLng(e.latitude, e.longitude)).toList());
  112. if (bounds == null) return;
  113. LatLng center = LatLng((bounds.northeast.latitude + bounds.southwest.latitude) / 2, (bounds.northeast.longitude + bounds.southwest.longitude) / 2);
  114. _mapController?.moveCamera(
  115. CameraUpdate.newLatLngBounds(bounds, 40.0)
  116. );
  117. // _mapController?.moveCamera(
  118. // CameraUpdate.newCameraPosition(CameraPosition(zoom: _zoom, bearing: -15, target: LatLng(center.latitude, center.longitude))),
  119. // );
  120. double zoom = calculateLatLngDistance(bounds.southwest, bounds.northeast) / 2000.0;
  121. print("22222222222222222222222222222 $_zoom ${15 - zoom}");
  122. _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(tilt: 45.0, zoom: min(_zoom, 15 - zoom) + 1.5, bearing: -15, target: LatLng(center.latitude, center.longitude))), animated: true, duration: 1000);
  123. }
  124. // await Future.delayed(Duration(seconds: 1));
  125. // _mapController?.moveCamera(
  126. // CameraUpdate.newCameraPosition(CameraPosition(zoom: _zoom, bearing: -270, target: LatLng(center.latitude, center.longitude))),
  127. // );
  128. setState(() {
  129. _markerMap[-1] = _markerMapReady[-1]!;
  130. });
  131. // _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(tilt: 45.0, zoom: _zoom, bearing: -15, target: LatLng(center.latitude, center.longitude))), animated: true, duration: 1000);
  132. // Future.delayed(Duration(seconds: 2)).then((value) => controller.moveCamera(CameraUpdate.newLatLngBounds(LatLngBoundsBuilder.fromList(points.map((e) => LatLng(e.latitude, e.longitude)).toList()), 50.0), animated: true, duration: 1000));
  133. await Future.delayed(Duration(seconds: 2));
  134. }
  135. _startAnimation();
  136. }
  137. _startAnimation() {
  138. List<Location> points = widget.points;
  139. int duration = widget.showType == 1 ? 16000 : widget.distance ~/ 1000 * 4000;
  140. _finish() {
  141. LatLngBounds? latLngBounds = LatLngBoundsBuilder.fromList(points.map((e) => LatLng(e.latitude, e.longitude)).toList());
  142. if (latLngBounds != null) {
  143. _mapController?.moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, 70.0), animated: true, duration: 1000);
  144. }
  145. setState(() {
  146. polylines.clear();
  147. polylines.addAll(points);
  148. if (widget.record == false) _mapReady = true;
  149. _markerMap.remove(0);
  150. _markerMap[1000] = _markerMapReady[1000]!;
  151. });
  152. // Future.delayed(Duration(seconds: 1)).then((value) =>
  153. // _mapController?.moveCamera(CameraUpdate.scrollBy(0, 100), animated: true),);
  154. MapNotification(this.distance, this.duration, this.step, finish: true).dispatch(context);
  155. }
  156. if (duration > 0 && widget.anim == true) {
  157. _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: duration))
  158. ..addStatusListener((status) {
  159. if (status == AnimationStatus.completed) {
  160. _finish();
  161. }
  162. });
  163. _animation = Tween(begin: 0.0, end: points.length).animate(
  164. _animationController!,
  165. )..addListener(() {
  166. var index = _animation?.value.toInt();
  167. if (mounted) {
  168. var list = points.sublist(0, index);
  169. if (list.isNotEmpty == true) {
  170. // _valueNotifier.value = list;
  171. // _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(target: LatLng(list.last.latitude, list.last.longitude), tilt: 45.0, zoom: 18, bearing: -15)));
  172. Location location = list.last;
  173. LatLng latLng = SportUtils.toLatLng(location);
  174. if (widget.showType == 0) {
  175. _mapController?.moveCamera(CameraUpdate.newLatLng(latLng));
  176. }
  177. if (_marker != null) {
  178. _marker = _marker!.copyWith(positionParam: latLng, rotationParam: (location.bearing ?? 0) - 90);
  179. _markerMap[0] = _marker!;
  180. }
  181. polylines.add(location);
  182. MapNotification notification = _calPoints(list);
  183. int km = notification.distance ~/ 1000;
  184. if (km != _perKm) {
  185. _perKm = km;
  186. _markerMap[km] = _markerMapReady[km]!;
  187. }
  188. notification.dispatch(context);
  189. setState(() {
  190. });
  191. }
  192. }
  193. });
  194. _animationController?.forward(from: 0.0);
  195. } else {
  196. _markerMap.addAll(_markerMapReady);
  197. this.distance = widget.distance;
  198. _finish();
  199. }
  200. }
  201. double distance = 0;
  202. int duration = 0;
  203. int step = 0;
  204. MapNotification _calPoints(List<Location> points) {
  205. distance = 0;
  206. duration = 0;
  207. step = 0;
  208. for (var i = 1; i < points.length; i++) {
  209. Location point = points[i];
  210. if (point.state == 1) continue;
  211. distance += calculateLineDistance(points[i - 1].latitude, points[i - 1].longitude, point.latitude, point.longitude);
  212. duration = point.time ?? 0;
  213. step = point.step ?? 0;
  214. }
  215. return MapNotification(distance, duration, step);
  216. }
  217. Future _fillMarker(List<Location> points) async {
  218. _markerMap.clear();
  219. _markerMapReady.clear();
  220. _markerMapReady[-1] = Marker(position: SportUtils.toLatLng(points.first), icon: BitmapDescriptor.fromIconPath("lib/assets/img/map_icon_start_replay.png"), anchor: Offset(0.5, 0.8), clickable: false, zIndex: 101, infoWindowEnable: false, anim: true);
  221. _markerMapReady[1000] = Marker(position: SportUtils.toLatLng(points.last), icon: BitmapDescriptor.fromIconPath("lib/assets/img/map_icon_over_replay.png"), anchor: Offset(0.5, 0.8), clickable: false, zIndex: 102, infoWindowEnable: false, anim: true);
  222. _marker = Marker(
  223. position: SportUtils.toLatLng(points.first),
  224. icon: BitmapDescriptor.fromIconPath("lib/assets/img/map_icon_direction_replay.png"),
  225. anchor: Offset(0.5, 0.5),
  226. clickable: false,
  227. zIndex: 103,
  228. infoWindowEnable: false,
  229. );
  230. _markerMapReady[0] = _marker!;
  231. if (widget.runMapKm == true && widget.pointsKm.isNotEmpty == true)
  232. for (var e in widget.pointsKm.entries) {
  233. await _updateLocationKm(e.key, LatLng(e.value.latitude, e.value.longitude));
  234. }
  235. }
  236. _updateLocationKm(int key, LatLng latLng) async {
  237. ui.PictureRecorder pictureRecorder = new ui.PictureRecorder(); // 图片记录仪
  238. Canvas canvas = new Canvas(pictureRecorder); //canvas接受一个图片记录仪
  239. ByteData data = await rootBundle.load("lib/assets/img/map_bg_kilometre_replay.png");
  240. ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
  241. ui.FrameInfo fi = await codec.getNextFrame();
  242. ui.Image image = fi.image;
  243. Paint _paint = new Paint()
  244. ..color = Colors.red
  245. ..isAntiAlias = true;
  246. canvas.drawImage(image, Offset(0, 0), _paint);
  247. final ui.ParagraphStyle _valueStyle = ui.ParagraphStyle(
  248. textAlign: TextAlign.center,
  249. fontSize: 32,
  250. );
  251. ui.ParagraphBuilder pb = ui.ParagraphBuilder(_valueStyle)
  252. ..pushStyle(ui.TextStyle(color: Colors.white))
  253. ..addText("$key");
  254. ui.ParagraphConstraints constraints = ui.ParagraphConstraints(width: image.width.toDouble());
  255. ui.Paragraph paragraph = pb.build()..layout(constraints);
  256. paragraph.computeLineMetrics().forEach((element) {
  257. canvas.drawParagraph(paragraph, Offset(0, (image.height - element.height) / 2 - element.descent));
  258. });
  259. ui.Image picture = await pictureRecorder.endRecording().toImage(image.width, image.height); //设置生成图片的宽和高
  260. ByteData? pngImageBytes = await picture.toByteData(format: ui.ImageByteFormat.png);
  261. if (pngImageBytes == null) return;
  262. Uint8List pngBytes = pngImageBytes.buffer.asUint8List();
  263. _markerMapReady[key] = Marker(
  264. position: latLng,
  265. // anchor: Offset(0.5, 0.5),
  266. icon: BitmapDescriptor.fromBytes(pngBytes),
  267. clickable: false,
  268. infoWindowEnable: false,
  269. zIndex: 100,
  270. anim: true);
  271. }
  272. Future _waitMapReady() async {
  273. Completer completer = Completer();
  274. Timer timer = Timer.periodic(Duration(seconds: 1), (timer) {
  275. if (_mapReady == true) {
  276. if (!completer.isCompleted) completer.complete(true);
  277. }
  278. });
  279. var result = await completer.future;
  280. timer.cancel();
  281. return result;
  282. }
  283. Future<File?> takeSnapshot() async {
  284. var path = await request(context, () async {
  285. await _waitMapReady();
  286. return takeAMapSnapshot(_mapController);
  287. });
  288. return path;
  289. }
  290. runAnimation() async {
  291. bool result = false;
  292. if (_mapController == null) return result;
  293. setState(() {
  294. _mapReady = false;
  295. polylines.clear();
  296. _markerMap.clear();
  297. });
  298. _mapController!.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(target: LatLng(widget.points.first.latitude, widget.points.first.longitude), zoom: 15, bearing: -270)));
  299. _onMapCreated(_mapController!);
  300. }
  301. @override
  302. Widget build(BuildContext context) {
  303. return AMapWidget(
  304. privacyStatement: amapPrivacyStatement,
  305. apiKey: AMapApiKey(androidKey: KEY_ANDROID, iosKey: KEY_IOS),
  306. mapType: widget.anim == true ? selectMapType(_mapReady ? widget.runMapType : 2) : selectMapType(widget.runMapType),
  307. onMapCreated: _onMapCreated,
  308. customStyleOptions: _mapReady ? customStyleOptions : null,
  309. initialCameraPosition: CameraPosition(target: LatLng(widget.points.first.latitude, widget.points.first.longitude), zoom: 15),
  310. buildingsEnabled: true,
  311. tiltGesturesEnabled: false,
  312. rotateGesturesEnabled: false,
  313. touchPoiEnabled: false,
  314. labelsEnabled: selectMapType(widget.runMapType) == MapType.navi ? false : _mapReady,
  315. scrollGesturesEnabled: _mapReady,
  316. scaleEnabled: _mapReady,
  317. gestureRecognizers: Set()..add(Factory<VerticalDragGestureRecognizer>(() => VerticalDragGestureRecognizer()))..add(Factory<HorizontalDragGestureRecognizer>(() => HorizontalDragGestureRecognizer())),
  318. markers: Set<Marker>.of(_markerMap.values),
  319. polylines: Set<Polyline>.of(SportUtils.splitLine(polylines, speed: _avgSpeed)),
  320. // onCameraMove: (position) {
  321. // _zoom = position.zoom;
  322. // },
  323. );
  324. }
  325. }
  326. class MapNotification extends Notification {
  327. final double distance;
  328. final int duration;
  329. final int step;
  330. final bool finish;
  331. MapNotification(this.distance, this.duration, this.step, {this.finish = false});
  332. }