/* * @Author: wangyang 1147192855@qq.com * @Date: 2022-07-23 11:55:16 * @LastEditors: wangyang 1147192855@qq.com * @LastEditTime: 2022-09-28 17:01:15 * @FilePath: \marking_app\lib\components\PictureOverview.dart * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */ import 'dart:async'; import 'dart:io'; import 'package:crypto/crypto.dart' as crypto; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:marking_app/common/mixin/common.dart'; import 'package:marking_app/common/model/common/base_structure_result.dart'; import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; import 'package:marking_app/common/model/enum/review_marks_bottom_btns_enum.dart'; import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; import 'package:marking_app/common/model/job/test_questions_image_info.dart'; import 'package:marking_app/common/model/job/upload_file_interface_config.dart'; import 'package:marking_app/common/model/job/upload_file_interface_config_params.dart'; import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; import 'package:marking_app/common/model/marking/marking_history_zoom_info.dart'; import 'package:marking_app/common/model/marking/marking_text_question.dart'; import 'package:marking_app/common/model/marking/switch_keyboard_to_reload_images.dart'; import 'package:marking_app/pages/common/event_bus_mixin.dart'; import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart'; import 'package:marking_app/pages/marking/hooks/use_zoom_image_history.dart'; import 'package:marking_app/pages/marking/provider/do_paper_bottom_review_marks_provider.dart'; import 'package:marking_app/pages/marking/provider/zoom_height_provider.dart'; import 'package:marking_app/pages/marking/provider/zoom_history_provider.dart'; import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; import 'package:marking_app/provider/do_marking_provider.dart'; import 'package:marking_app/provider/upload_file_provider.dart'; import 'package:marking_app/utils/index.dart'; import 'package:marking_app/utils/my_text.dart'; import 'package:marking_app/utils/request/rest_client.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:ui' as ui; import 'package:uuid/uuid.dart'; import 'package:zoom_widget/zoom_widget.dart'; import '../pages/marking/provider/draw_marking_provider.dart'; part 'PictureOverview.g.dart'; typedef PageChanged = void Function(int index); //这里就是关键的代码,定义一个key final GlobalKey pictureOverviewKey = GlobalKey(); class PictureOverview extends StatefulHookConsumerWidget { final double imageScale; final Offset? imagePosition; final String questionNum; final int markingUserId; final bool homework; final List imageItems; //图片列表 final bool annotationsFlag; final String testQuestionNumber; final Map commentImageMap; final MarkingTextQuestion data; final Function callAnnotationTips; const PictureOverview({ required this.imageItems, required this.annotationsFlag, required this.commentImageMap, required this.testQuestionNumber, required this.questionNum, required this.markingUserId, required this.data, required this.callAnnotationTips, this.homework = false, this.imageScale = 1, this.imagePosition, Key? key, }) : super(key: key); @override PictureOverviewState createState() => PictureOverviewState(); } class PictureOverviewState extends ConsumerState with CommonMixin, EventBusMixin { final GlobalKey theglobalKey = GlobalKey(); final GlobalKey<_ExamPaperDrawingState> examPaperDrawingKey = GlobalKey<_ExamPaperDrawingState>(); final GlobalKey zoomGlobalKey = GlobalKey(); // zoom double? initScale; Offset? zoomOffset; ImageStreamListener? _imageStreamListener; TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 ImageStream? _imageStream; final int currentIndex = 0; late AnnotationGraffitiSwitch graffitiSwitch; late RemoveListener _annotationsListener; // 批注关闭监听 File? temFile; // 批注临时数据 // 用于记录绘图结果的变量 Offset? globalPosition; // 是否正在绘制 MarkingHistoryZoomInfo? zoomInfo; bool illegalArea = false; // 非法区域(批阅笔迹不在试题图片中) final GlobalKey _zoomKey = GlobalKey>(); @override void initState() { super.initState(); FastData.getInstance().getMarkingZoomInfo().then((value) { if (value == null) return; bool flag = value.questionNum == widget.questionNum && value.markingUserId == widget.markingUserId; if (flag) { if (value.positionX != 0 && value.positionY != 0) { // zoomOffset = Offset(value.positionX, value.positionY); } if (value.scale < 1) { initScale = value.scale; // 5 Future.delayed(Duration(seconds: 1), () => ref.read(zoomHistoryProvider.notifier).setState(initScale!)); } } }); Future.delayed(Duration.zero, () { ref.read(zoomHeightProvider.notifier).setState(0.0); }); _annotationsListener = ref.read(annotationGraffitiSwitchProvider.notifier).addListener((state) { graffitiSwitch = state; toUpState(setState, () {}, mounted); }); // 事件总线监听 清空数据 eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) async { widget.callAnnotationTips(); // 调用回调 通知父级批注记录被更改 if (ref.read(drawMarkingProvider).data.isEmpty) { if (widget.data.commentImageUrl.isNotEmpty) { bool? res = await showDialog( // 表示点击灰色背景的时候是否消失弹出框 barrierDismissible: false, context: context, builder: (context1) { return AlertDialog(content: quickText("是否撤销上次批阅批注痕迹"), actions: [ TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)), TextButton(child: quickText("确定", color: Theme.of(context).primaryColor), onPressed: () => Navigator.pop(context1, true)) ]); }, ); if (res == true) { widget.data.commentImageUrl.removeAt(currentIndex); var theUrl = widget.imageItems[currentIndex]; widget.commentImageMap[theUrl] = theUrl; toUpState(setState, () {}, mounted); } } else { ToastUtils.showInfo('批注已清空'); } } }); } @override void dispose() { _annotationsListener(); eventCancel(); try { _imageStream?.removeListener(_imageStreamListener!); if (temFile != null) { bool flieExist = temFile!.existsSync(); if (flieExist) temFile!.delete(); } // if (zoomOffset != null) saveZoomPosition(); } catch (e) {} super.dispose(); } Future saveImage() async { try { ToastUtils.showLoading(); if (temFile == null) { if (examPaperDrawingKey.currentState?.pointsPureData.isEmpty ?? true) return null; // 没有图片就上传图片 RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) return null; // double dpr = MediaQuery.of(context).devicePixelRatio; double pixelRatio = MediaQuery.of(context).devicePixelRatio; if (imagInfoModel?.pixelRatio != null) pixelRatio = imagInfoModel!.pixelRatio; ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); if (byteData == null) { /// 等于null 已经是异常了 return null; } Directory appDocDir = await getApplicationDocumentsDirectory(); List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 File file = File(filePath); // 创建文件对象 await file.writeAsBytes(bytes); // 将ByteData写入文件 temFile = file; } if (imagInfoModel != null) { // 剪切图片 } crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes()); RestClient _client = await getClient(); BaseStructureResult resUploadConfig = await _client.getUploadFile(UploadFileInterfaceConfigParams( fileName: '1.png', fileMd5: fileMd5.toString(), contentLength: temFile!.lengthSync(), )); if (!resUploadConfig.success || resUploadConfig.data == null) { ToastUtils.getFluttertoast(msg: '获取图片上传配置失败', context: context); return null; } if (resUploadConfig.data!.uploadUri == null) { return FileResult(myObject: '', success: true, url: resUploadConfig.data!.downloadUri); } FileResult? resFile = await ref.read(uploadFileProvider.notifier).getUploadFileConfig(resUploadConfig.data!, temFile!); // FileResult? resFile = await ref // .read(uploadFileProvider.notifier) // .uploadFile(temFile!.path, widget.imageItems[currentIndex], ref.read(userProvider).id.toString()); if (resFile != null && resFile.success) { resFile.otherParam = currentIndex; return resFile; } } catch (e) { toPrint(val: e.toString()); } finally { // ToastUtils.dismiss(); } return null; } // 缩放组件 ==> 位置更新 void onPositionUpdate(Offset position) async { MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); double? scale = historyZoomInfo?.scale ?? 1; MarkingHistoryZoomInfo info = MarkingHistoryZoomInfo( scale: scale, positionX: position.dx, positionY: position.dy, questionNum: widget.questionNum, markingUserId: widget.markingUserId, ); FastData.getInstance().setMarkingZoomInfo(info); } void saveZoomPosition() async { MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); if (historyZoomInfo != null) { FastData.getInstance().setMarkingZoomInfo(MarkingHistoryZoomInfo( scale: historyZoomInfo.scale, positionX: zoomOffset!.dx, positionY: zoomOffset!.dy, questionNum: historyZoomInfo.questionNum, markingUserId: historyZoomInfo.markingUserId, )); } } void onPanUpPosition(Offset val) async { // 手指在移动 非物体移动的位置 // print('**************** 正在移动位置 YYY:${val.dy}'); // print('**************** 正在移动位置 XXX:${val.dx}'); zoomOffset = val; } // 缩放组件 ==> 缩放监听 void onScaleUpdate(double scale, double zoom) async { // print('zoom:$zoom'); // print('scale:${scale}'); // MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); double positionX = historyZoomInfo?.positionX ?? 0; double positionY = historyZoomInfo?.positionY ?? 0; MarkingHistoryZoomInfo info = MarkingHistoryZoomInfo( scale: zoom, positionX: positionX, positionY: positionY, questionNum: widget.questionNum, markingUserId: widget.markingUserId, ); zoomInfo = info; // if (double.parse(zoom.toStringAsFixed(2)) <= 1) zoom = 1; if (imagInfoModel != null) { // 根据缩放比例重置被放大的图片的尺寸 imagInfoModel = TestQuestionsImageInfo( // 获取图片的宽高 boxHeight: imagInfoModel!.boxHeight, boxWidth: imagInfoModel!.boxWidth, url: imagInfoModel!.url, height: imagInfoModel!.height, width: imagInfoModel!.width, zoom: zoom, ); } FastData.getInstance().setMarkingZoomInfo(info); } @override Widget build(BuildContext context) { return Container( width: double.infinity, height: double.infinity, alignment: Alignment.center, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { double containerWidth = constraints.maxWidth; double containerHeight = constraints.maxHeight; return $LocalAndNetworkSwitch( zoomGlobalKey: zoomGlobalKey, containerWidth: containerWidth, containerHeight: containerHeight, imagePosition: widget.imagePosition ?? Offset(0, 0), imageScale: widget.imageScale, homework: widget.homework, theglobalKey: theglobalKey, graffitiSwitch: graffitiSwitch, drawFlag: widget.annotationsFlag, examGlobalKey: examPaperDrawingKey, testQuestionNumber: widget.testQuestionNumber, imageUrl: widget.commentImageMap[widget.imageItems[currentIndex]]!, updateTempFileCall: (File? file) { temFile = file; print('更新需要上传的文件'); }, imageBuilder: (imageBuilderContext, imageProvider) { Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); if (imagInfoModel == null || (imagInfoModel?.boxHeight != containerHeight || imagInfoModel?.boxWidth != containerWidth)) { if (_imageStreamListener != null) _imageStream?.removeListener(_imageStreamListener!); _imageStreamListener = ImageStreamListener((ImageInfo info, bool _) { imagInfoModel = TestQuestionsImageInfo( // 获取图片的宽高 boxHeight: containerHeight, boxWidth: containerWidth, url: widget.commentImageMap[widget.imageItems[currentIndex]]!, height: info.image.height.toDouble(), width: info.image.width.toDouble(), ); Future.delayed(Duration.zero, () { ref.read(zoomHeightProvider.notifier).setState(imagInfoModel?.scaleHeight ?? 0.0); }); }); _imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(_imageStreamListener!); } var btnEnum = ref.watch(doPaperBottomReviewMarksProvider); // return imageWidget; return Listener( behavior: HitTestBehavior.opaque, onPointerMove: (PointerMoveEvent details) { if (btnEnum != ReviewMarksBottomBtnsEnum.HANDWRITING) return; if (globalPosition != null) { // 预防双指同时作用于屏幕 double dx = globalPosition!.dx; double dy = globalPosition!.dy; double dxNew = details.localPosition.dx; double dyNew = details.localPosition.dy; if ((dxNew - dx).abs() > 22 || (dyNew - dy).abs() > 22) return; } globalPosition = details.localPosition; Offset localPosition = globalPosition!; // if (imagInfoModel != null && // (localPosition.dy < imagInfoModel!.imageHeightOffsetStart! || localPosition.dy > imagInfoModel!.imageHeightOffsetend!)) { // // 笔迹画出图片区域 直接断笔 // var dataVal = ref.read(drawMarkingProvider).data; // if (dataVal.length - 1 > -1 && dataVal[dataVal.length - 1].data != null) { // var newVal = ref.read(drawMarkingProvider).data..add(GestureRecording(eraser: graffitiSwitch.openEraser)); // var newVal1 = ref.read(drawMarkingProvider).offsets..add(null); // ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(newVal, newVal1)); // } // illegalArea = true; // return; // } // illegalArea = false; var _theKey = _zoomKey.currentState; print(_theKey); double remainingHeight = imagInfoModel!.imageHeightOffsetStart!; // 剩余高度 if (remainingHeight > 1) { localPosition = Offset(localPosition.dx, localPosition.dy - remainingHeight); } // print(localPosition.dy); print(localPosition.dx); double _theZoomVal = imagInfoModel?.zoom ?? 1; var _dx = zoomOffset?.dx ?? 0; _dx = _dx > 0 ? 0 : _dx.abs() / _theZoomVal; var _dy = zoomOffset?.dy ?? 0; _dy = _dy > 0 ? 0 : _dy.abs() / _theZoomVal; if (_theZoomVal > 1) { // 计算视图被放大比例 还原笔迹坐标 localPosition = Offset(localPosition.dx / _theZoomVal, localPosition.dy / _theZoomVal); if (zoomOffset != null) { // 如果滚动条有触动就加上滚动条滚动的位置 localPosition = Offset(localPosition.dx + _dx, localPosition.dy + _dy); } } else if (_theZoomVal < 1) { // 试图被缩小 double imgSpaceWidthOfSingle = (imagInfoModel!.boxWidth - imagInfoModel!.scaleWidth!) / 2; localPosition = Offset((localPosition.dx - imgSpaceWidthOfSingle) / _theZoomVal, localPosition.dy / _theZoomVal + _dy); // localPosition = Offset(localPosition.dx * _theZoomVal - imgSpaceWidthOfSingle, localPosition.dy / _theZoomVal); } else { localPosition = Offset(localPosition.dx, localPosition.dy + _dy); } var newVal = ref.read(drawMarkingProvider).data..add(GestureRecording(eraser: graffitiSwitch.openEraser, data: localPosition)); var newVal1 = ref.read(drawMarkingProvider).offsets..add(localPosition); ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(newVal, newVal1)); widget.callAnnotationTips(); }, // onPointerDown: (PointerDownEvent event) { // }, onPointerUp: (PointerUpEvent details) { if (btnEnum != ReviewMarksBottomBtnsEnum.HANDWRITING) return; // 如果在空白区域 非试题图片区域就返回 if (illegalArea) return; globalPosition = null; var newVal = ref.read(drawMarkingProvider).data..add(GestureRecording(eraser: graffitiSwitch.openEraser)); var newVal1 = ref.read(drawMarkingProvider).offsets..add(null); ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(newVal, newVal1)); }, child: IgnorePointer( ignoring: btnEnum != ReviewMarksBottomBtnsEnum.DRAG, child: Zoom( key: _zoomKey, // initTotalZoomOut: true, child: ExamPaperDrawing( key: examPaperDrawingKey, globalKey: theglobalKey, child: imageWidget, graffitiSwitch: graffitiSwitch, decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)), ), maxZoomWidth: containerWidth, canvasColor: Colors.transparent, backgroundColor: Colors.transparent, maxZoomHeight: imagInfoModel?.scaleHeight != null ? (imagInfoModel!.scaleHeight! / imagInfoModel!.zoom) : null, initScale: initScale ?? 1, initPosition: zoomOffset, onScaleUpdate: onScaleUpdate, onPositionUpdate: onPanUpPosition, ), ), ); }, ); }, )); } } // 试卷绘制 class ExamPaperDrawing extends StatefulHookConsumerWidget { // String imgUrl; Widget child; BoxDecoration? decoration; AnnotationGraffitiSwitch graffitiSwitch; GlobalKey globalKey; // Function(String) imageCall; ExamPaperDrawing({ // required this.imgUrl, required this.child, required this.graffitiSwitch, required this.globalKey, this.decoration, Key? key, }) : super(key: key); @override _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); } class _ExamPaperDrawingState extends ConsumerState with EventBusMixin { // 用于记录手指位置的变量 late RemoveListener removeListener; late ValueNotifier> _vnHandWritings; late List pointsPureData = []; @override void initState() { _vnHandWritings = ValueNotifier>([]); removeListener = ref.read(drawMarkingProvider.notifier).addListener((state) { pointsPureData = state.offsets; _vnHandWritings.value = [...state.data]; }, fireImmediately: false); // 事件总线监听 eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) { if (item.previousStep) { if (ref.read(drawMarkingProvider).data.isEmpty) { return; } var index = pointsPureData.toList().lastIndexOf(null); if (index != -1) { if (index + 1 == pointsPureData.length) { pointsPureData = pointsPureData.sublist(0, index); ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(ref.read(drawMarkingProvider).data.sublist(0, index), pointsPureData)); index = pointsPureData.toList().lastIndexOf(null); index == -1 ? -1 : index + 1; } if (index != -1) { pointsPureData = pointsPureData.sublist(0, index); ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(ref.read(drawMarkingProvider).data.sublist(0, index), pointsPureData)); } else { item.cleanAll = true; } } else { item.cleanAll = true; } } if (item.cleanAll) { pointsPureData.clear(); ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal([], [])); } }); super.initState(); } @override void dispose() { eventCancel(); removeListener(); _vnHandWritings.dispose(); super.dispose(); } @override Widget build(BuildContext context) { print('_ExamPaperDrawingState的build....'); return RepaintBoundary( key: widget.globalKey, child: CustomPaint( isComplex: true, willChange: true, foregroundPainter: DrawingPainter(ctrl: _vnHandWritings), child: widget.child, ), ); } } class DrawingPainter extends CustomPainter { final ValueNotifier> ctrl; final Paint paintBrush = Paint(); DrawingPainter({required this.ctrl}) : super(repaint: ctrl) { paintBrush ..color = Colors.red ..strokeCap = StrokeCap.round ..strokeWidth = 1.5.r; } @override void paint(Canvas canvas, Size size) { var points = ctrl.value; var pointsLength = points.length; print('数据.....................[[[[[${points.length}]]]]]'); for (int i = 0; i < pointsLength; i++) { GestureRecording item = points[i]; Offset? offsetData = item.data; Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1].data; if (offsetData != null && nextOffsetData != null) { canvas.drawLine(offsetData, nextOffsetData, paintBrush); } } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { print('FFFFFF55555555555555'); // if (oldDelegate is DrawingPainter) { // var repaint = ctrl.value.length != oldDelegate.ctrl.value.length || oldDelegate.ctrl.value != ctrl.value; // print('调用是否绘制:$repaint'); // return repaint; // } return false; } } /** * 手势记录 */ class GestureRecording { bool eraser; // 是否是橡皮擦 Offset? data; // 位置记录 可能为null GestureRecording({required this.eraser, this.data}); } @hwidget Widget $myCachedNetworkImage({ required String imageUrl, required File? tempFile, required double width, required double height, required ImageWidgetBuilder? imageBuilder, }) { UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use(); if (tempFile != null) { // 注释临时本地图片 return Image.file(tempFile, fit: BoxFit.contain, width: double.infinity, height: double.infinity); } useEffect(() { _useImgRefsh.eventOnSub(callback: (SwitchKeyboardToReloadImages event) { if (event.reload) { _useImgRefsh.imageKey.value = UniqueKey(); } }); return () { _useImgRefsh.eventCancelSub(); }; }, []); return CacheNetImageView( cacheNetImageKey: _useImgRefsh.imageKey.value, imageUrl: imageUrl, imageBuilder: imageBuilder, errorWidget: (context, url, error) { return GestureDetector( onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset('assets/images/test_paper_loading_failed.png'), quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), ], ), ); }, ); } class CacheNetImageView extends ConsumerWidget { final Key cacheNetImageKey; final String imageUrl; final ImageWidgetBuilder? imageBuilder; final LoadingErrorWidgetBuilder? errorWidget; const CacheNetImageView({ required this.cacheNetImageKey, required this.imageUrl, required this.imageBuilder, required this.errorWidget, super.key, }); @override Widget build(BuildContext context, WidgetRef ref) { double zoomHeight = ref.watch(zoomHeightProvider); double initScale = ref.watch(zoomHistoryProvider); print('zoomView的视图最大高度:$zoomHeight 和 缩放比例$initScale'); return CachedNetworkImage( key: cacheNetImageKey, imageUrl: imageUrl, fit: BoxFit.fitWidth, width: double.infinity, alignment: Alignment.center, imageBuilder: imageBuilder, placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), errorWidget: errorWidget, ); } } // double zoomHeight = ref.watch(zoomHeightProvider); /// 网络图和本地图切换 @hwidget Widget $localAndNetworkSwitch( BuildContext context, { double imageScale = 1, Offset imagePosition = const Offset(0, 0), required ImageWidgetBuilder? imageBuilder, required GlobalKey zoomGlobalKey, required double containerWidth, required double containerHeight, required String testQuestionNumber, // 试题题号 required String imageUrl, // 图片 required bool drawFlag, // 是否打开绘画操作 required GlobalKey theglobalKey, required bool homework, required GlobalKey<_ExamPaperDrawingState> examGlobalKey, required AnnotationGraffitiSwitch graffitiSwitch, required Function(File?) updateTempFileCall, }) { // ImageStream? imageStream; // 图片监听数据 // TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 // ImageStreamListener theImageStreamListener; UseLocalAndNetworkSwitch _useSwitch = UseLocalAndNetworkSwitch.use(!homework); UseZoomImageHistory _useZoomHistory = UseZoomImageHistory.use(testQuestionNumber); // TransformationController _transContller = useTransformationController(); useValueChanged( _useSwitch.temFile.value, (oldValue, oldResult) { updateTempFileCall(_useSwitch.temFile.value); return null; }, ); useValueChanged(testQuestionNumber, (oldValue, oldResult) { if (testQuestionNumber.length > 0) { _useZoomHistory.initInfo(testQuestionNumber); } }); useValueChanged(drawFlag, (oldValue, oldResult) { if (!drawFlag) { // 关闭的时候创建临时图片文件在设备 _useSwitch.createTempFile(context, theglobalKey: theglobalKey, examGlobalKey: examGlobalKey).then((File? theFile) { if (theFile == null) { // TODO 代表保存失败的逻辑 // 当前情况:_useSwich.showZoomImg.value 没有设置为true还是展示的原来的绘图组件ExamPaperDrawing toPrint(val: '进入错误逻辑.........'); } _useSwitch.showZoomImg.value = true; }); return; } _useSwitch.showZoomImg.value = !drawFlag; }); useEffect(() { _useZoomHistory.initInfo(); // 初始化历史数据 // _useScrollController // ..addListener(() { // _scrollPosition.value = _useScrollController.position.pixels; // }); return () { // try { // _useImageSize.imageStream.value?.removeListener(_useImageSize.imageListener.value!); // } catch (e) {} // ..removeListener(() {}) }; }, []); print('是否更新视图.... ${_useZoomHistory.initPosition.value}'); return $MyCachedNetworkImage( imageUrl: imageUrl, tempFile: _useSwitch.temFile.value, width: containerWidth, height: containerHeight, imageBuilder: imageBuilder, ); } class UseLocalAndNetworkSwitch { ValueNotifier showZoomImg; ValueNotifier temFile; ValueNotifier?> points; ValueNotifier?> pointsPureData; ValueNotifier> imageLoaded; UseLocalAndNetworkSwitch._({ required this.showZoomImg, required this.temFile, required this.points, required this.pointsPureData, required this.imageLoaded, }); // 工厂构造函数 factory UseLocalAndNetworkSwitch.use(bool defaultVal) { return UseLocalAndNetworkSwitch._( points: useState(null), pointsPureData: useState(null), showZoomImg: useState(defaultVal), temFile: useState(null), imageLoaded: useState({}), ); } Future createTempFile( BuildContext context, { required GlobalKey theglobalKey, required GlobalKey<_ExamPaperDrawingState> examGlobalKey, }) async { Timer? _timer; try { _timer = Timer(Duration(seconds: 1), () { // 执行操作的代码 ToastUtils.showLoading(); }); if (examGlobalKey.currentState?.pointsPureData.isEmpty ?? true) { try { temFile.value?.delete(); } catch (e) {} temFile.value = null; return null; } RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) return null; ui.Image image = await boundary.toImage(pixelRatio: 3.0); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); if (byteData != null) { Directory appDocDir = await getApplicationDocumentsDirectory(); List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 File file = File(filePath); // 创建文件对象 await file.writeAsBytes(bytes); // 将ByteData写入文件 temFile.value?.delete(); temFile.value = file; // 保存临时文件 points.value = examGlobalKey.currentState?._vnHandWritings.value; pointsPureData.value = examGlobalKey.currentState?.pointsPureData; toPrint(val: '图片保存成功:'); return temFile.value; } } catch (e) { toPrint(val: '图片生成错误:${e}'); toPrint(val: e.toString()); ToastUtils.getFluttertoast(context: context, msg: '保存图片报错,请稍后重试'); } finally { _timer?.cancel(); ToastUtils.dismiss(); } return null; } }