import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:amap_flutter_base/amap_flutter_base.dart'; import 'package:amap_flutter_map/amap_flutter_map.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sport/pages/run/location.dart'; import 'package:sport/pages/run/run_start.dart'; import 'package:sport/pages/run/setting_page.dart'; import 'package:sport/utils/path_smooth_tool.dart'; import 'package:sport/utils/sport_utils.dart'; import 'package:sport/widgets/dialog/request_dialog.dart'; Future getFile({String suffix = "png"}) async { String sTempDir = (await getTemporaryDirectory()).path; bool isDirExist = await Directory(sTempDir).exists(); if (!isDirExist) { Directory(sTempDir).create(); } var now = DateTime.now(); File file = File(sTempDir + "/poster-temp-${now.millisecondsSinceEpoch}-${Random().nextInt(100)}.$suffix"); return file; } Future takeAMapSnapshot(AMapController? mapController) async { File file = await getFile(); if (mapController != null) { var mapData = await mapController.takeSnapshot(); if(mapData != null) file.writeAsBytesSync(mapData); } return file; } Future takeAMapSnapshotData(AMapController? mapController) async { if (mapController != null) { return await mapController.takeSnapshot(); } return null; } class MapWidget extends StatefulWidget { final double distance; final int runMapType; final bool runMapKm; final List points; final Map pointsKm; final bool record; final bool anim; final int showType; 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); @override State createState() => MapState(); } class MapState extends State with RunSetting, TickerProviderStateMixin { AMapController? _mapController; final Map _markerMap = {}; final Map _markerMapReady = {}; AnimationController? _animationController; Animation? _animation; Marker? _marker; late double _avgSpeed; int _perKm = 0; List polylines = []; bool _mapReady = false; double _zoom = 15; @override void initState() { super.initState(); List points = widget.points; _avgSpeed = points.map((e) => e.speed ?? 0).reduce((value, element) => value + element) / points.length; runMapType = widget.runMapType; loadCustomData(); } @override void dispose() { _animationController?.dispose(); _mapController?.disponse(); _mapController = null; super.dispose(); } @override void didUpdateWidget(covariant MapWidget oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.runMapKm != widget.runMapKm) { _fillMarker(widget.points).then((value) => setState(() { _markerMap.addAll(_markerMapReady); _markerMap.remove(0); })); } if (oldWidget.runMapType != widget.runMapType) { runMapType = widget.runMapType; loadCustomData(); } } void _onMapCreated(AMapController controller) async { _mapController = controller; if(widget.anim != true){ initAnimation(); } } initAnimation() async{ List points = widget.points; await _fillMarker(points); if (widget.anim == true) { if (widget.showType == 0) { _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(tilt: 45.0, zoom: 17, bearing: -15, target: LatLng(points.first.latitude, points.first.longitude))), animated: true, duration: 1000); } else { LatLngBounds? bounds = LatLngBoundsBuilder.fromList(points.map((e) => LatLng(e.latitude, e.longitude)).toList()); if (bounds == null) return; LatLng center = LatLng((bounds.northeast.latitude + bounds.southwest.latitude) / 2, (bounds.northeast.longitude + bounds.southwest.longitude) / 2); _mapController?.moveCamera( CameraUpdate.newLatLngBounds(bounds, 40.0) ); // _mapController?.moveCamera( // CameraUpdate.newCameraPosition(CameraPosition(zoom: _zoom, bearing: -15, target: LatLng(center.latitude, center.longitude))), // ); double zoom = calculateLatLngDistance(bounds.southwest, bounds.northeast) / 2000.0; print("22222222222222222222222222222 $_zoom ${15 - zoom}"); _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); } // await Future.delayed(Duration(seconds: 1)); // _mapController?.moveCamera( // CameraUpdate.newCameraPosition(CameraPosition(zoom: _zoom, bearing: -270, target: LatLng(center.latitude, center.longitude))), // ); setState(() { _markerMap[-1] = _markerMapReady[-1]!; }); // _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(tilt: 45.0, zoom: _zoom, bearing: -15, target: LatLng(center.latitude, center.longitude))), animated: true, duration: 1000); // 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)); await Future.delayed(Duration(seconds: 2)); } _startAnimation(); } _startAnimation() { List points = widget.points; int duration = widget.showType == 1 ? 16000 : widget.distance ~/ 1000 * 4000; _finish() { LatLngBounds? latLngBounds = LatLngBoundsBuilder.fromList(points.map((e) => LatLng(e.latitude, e.longitude)).toList()); if (latLngBounds != null) { _mapController?.moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, 70.0), animated: true, duration: 1000); } setState(() { polylines.clear(); polylines.addAll(points); if (widget.record == false) _mapReady = true; _markerMap.remove(0); _markerMap[1000] = _markerMapReady[1000]!; }); // Future.delayed(Duration(seconds: 1)).then((value) => // _mapController?.moveCamera(CameraUpdate.scrollBy(0, 100), animated: true),); MapNotification(this.distance, this.duration, this.step, finish: true).dispatch(context); } if (duration > 0 && widget.anim == true) { _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: duration)) ..addStatusListener((status) { if (status == AnimationStatus.completed) { _finish(); } }); _animation = Tween(begin: 0.0, end: points.length).animate( _animationController!, )..addListener(() { var index = _animation?.value.toInt(); if (mounted) { var list = points.sublist(0, index); if (list.isNotEmpty == true) { // _valueNotifier.value = list; // _mapController?.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(target: LatLng(list.last.latitude, list.last.longitude), tilt: 45.0, zoom: 18, bearing: -15))); Location location = list.last; LatLng latLng = SportUtils.toLatLng(location); if (widget.showType == 0) { _mapController?.moveCamera(CameraUpdate.newLatLng(latLng)); } if (_marker != null) { _marker = _marker!.copyWith(positionParam: latLng, rotationParam: (location.bearing ?? 0) - 90); _markerMap[0] = _marker!; } polylines.add(location); MapNotification notification = _calPoints(list); int km = notification.distance ~/ 1000; if (km != _perKm) { _perKm = km; _markerMap[km] = _markerMapReady[km]!; } notification.dispatch(context); setState(() { }); } } }); _animationController?.forward(from: 0.0); } else { _markerMap.addAll(_markerMapReady); this.distance = widget.distance; _finish(); } } double distance = 0; int duration = 0; int step = 0; MapNotification _calPoints(List points) { distance = 0; duration = 0; step = 0; for (var i = 1; i < points.length; i++) { Location point = points[i]; if (point.state == 1) continue; distance += calculateLineDistance(points[i - 1].latitude, points[i - 1].longitude, point.latitude, point.longitude); duration = point.time ?? 0; step = point.step ?? 0; } return MapNotification(distance, duration, step); } Future _fillMarker(List points) async { _markerMap.clear(); _markerMapReady.clear(); _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); _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); _marker = Marker( position: SportUtils.toLatLng(points.first), icon: BitmapDescriptor.fromIconPath("lib/assets/img/map_icon_direction_replay.png"), anchor: Offset(0.5, 0.5), clickable: false, zIndex: 103, infoWindowEnable: false, ); _markerMapReady[0] = _marker!; if (widget.runMapKm == true && widget.pointsKm.isNotEmpty == true) for (var e in widget.pointsKm.entries) { await _updateLocationKm(e.key, LatLng(e.value.latitude, e.value.longitude)); } } _updateLocationKm(int key, LatLng latLng) async { ui.PictureRecorder pictureRecorder = new ui.PictureRecorder(); // 图片记录仪 Canvas canvas = new Canvas(pictureRecorder); //canvas接受一个图片记录仪 ByteData data = await rootBundle.load("lib/assets/img/map_bg_kilometre_replay.png"); ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); ui.FrameInfo fi = await codec.getNextFrame(); ui.Image image = fi.image; Paint _paint = new Paint() ..color = Colors.red ..isAntiAlias = true; canvas.drawImage(image, Offset(0, 0), _paint); final ui.ParagraphStyle _valueStyle = ui.ParagraphStyle( textAlign: TextAlign.center, fontSize: 32, ); ui.ParagraphBuilder pb = ui.ParagraphBuilder(_valueStyle) ..pushStyle(ui.TextStyle(color: Colors.white)) ..addText("$key"); ui.ParagraphConstraints constraints = ui.ParagraphConstraints(width: image.width.toDouble()); ui.Paragraph paragraph = pb.build()..layout(constraints); paragraph.computeLineMetrics().forEach((element) { canvas.drawParagraph(paragraph, Offset(0, (image.height - element.height) / 2 - element.descent)); }); ui.Image picture = await pictureRecorder.endRecording().toImage(image.width, image.height); //设置生成图片的宽和高 ByteData? pngImageBytes = await picture.toByteData(format: ui.ImageByteFormat.png); if (pngImageBytes == null) return; Uint8List pngBytes = pngImageBytes.buffer.asUint8List(); _markerMapReady[key] = Marker( position: latLng, // anchor: Offset(0.5, 0.5), icon: BitmapDescriptor.fromBytes(pngBytes), clickable: false, infoWindowEnable: false, zIndex: 100, anim: true); } Future _waitMapReady() async { Completer completer = Completer(); Timer timer = Timer.periodic(Duration(seconds: 1), (timer) { if (_mapReady == true) { if (!completer.isCompleted) completer.complete(true); } }); var result = await completer.future; timer.cancel(); return result; } Future takeSnapshot() async { var path = await request(context, () async { await _waitMapReady(); return takeAMapSnapshot(_mapController); }); return path; } runAnimation() async { bool result = false; if (_mapController == null) return result; setState(() { _mapReady = false; polylines.clear(); _markerMap.clear(); }); _mapController!.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(target: LatLng(widget.points.first.latitude, widget.points.first.longitude), zoom: 15, bearing: -270))); _onMapCreated(_mapController!); } @override Widget build(BuildContext context) { return AMapWidget( privacyStatement: amapPrivacyStatement, apiKey: AMapApiKey(androidKey: KEY_ANDROID, iosKey: KEY_IOS), mapType: widget.anim == true ? selectMapType(_mapReady ? widget.runMapType : 2) : selectMapType(widget.runMapType), onMapCreated: _onMapCreated, customStyleOptions: _mapReady ? customStyleOptions : null, initialCameraPosition: CameraPosition(target: LatLng(widget.points.first.latitude, widget.points.first.longitude), zoom: 15), buildingsEnabled: true, tiltGesturesEnabled: false, rotateGesturesEnabled: false, touchPoiEnabled: false, labelsEnabled: selectMapType(widget.runMapType) == MapType.navi ? false : _mapReady, scrollGesturesEnabled: _mapReady, scaleEnabled: _mapReady, gestureRecognizers: Set()..add(Factory(() => VerticalDragGestureRecognizer()))..add(Factory(() => HorizontalDragGestureRecognizer())), markers: Set.of(_markerMap.values), polylines: Set.of(SportUtils.splitLine(polylines, speed: _avgSpeed)), // onCameraMove: (position) { // _zoom = position.zoom; // }, ); } } class MapNotification extends Notification { final double distance; final int duration; final int step; final bool finish; MapNotification(this.distance, this.duration, this.step, {this.finish = false}); }