/* * @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/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/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/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 'package:zoom_widget/zoom_widget.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; const PictureOverview({ required this.imageItems, required this.annotationsFlag, required this.commentImageMap, required this.testQuestionNumber, required this.questionNum, required this.markingUserId, this.homework = false, this.imageScale = 1, this.imagePosition, Key? key, }) : super(key: key); @override PictureOverviewState createState() => PictureOverviewState(); } class PictureOverviewState extends ConsumerState with CommonMixin { 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; // 批注临时数据 @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; Future.delayed(Duration(seconds: 5), () => 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); }); } @override void dispose() { super.dispose(); _annotationsListener(); try { _imageStream?.removeListener(_imageStreamListener!); if (temFile != null) { bool flieExist = temFile!.existsSync(); if (flieExist) temFile!.delete(); } } catch (e) {} } 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 dpr = ui.window.devicePixelRatio; ui.Image image = await boundary.toImage(pixelRatio: dpr); 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; } 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 onScaleUpdate(double scale, double scale1) async { print('这是第一个scale:$scale'); print('这是第二个noScale:$scale1'); // 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: scale1, positionX: positionX, positionY: positionY, questionNum: widget.questionNum, markingUserId: widget.markingUserId, ); FastData.getInstance().setMarkingZoomInfo(info); } @override Widget build(BuildContext context) { DoMarkingKeyboardModel _model = ref.watch(markingKeyboardProvider); 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: (context, 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(), ); printJson(imagInfoModel!.toJson()); Future.delayed(Duration.zero, () { ref.read(zoomHeightProvider.notifier).setState(imagInfoModel?.scaleHeight ?? 0.0); }); }); _imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(_imageStreamListener!); } // return imageWidget; return Zoom( // initTotalZoomOut: true, child: imageWidget, maxZoomWidth: containerWidth, canvasColor: Colors.transparent, backgroundColor: Colors.transparent, maxZoomHeight: imagInfoModel?.scaleHeight, initScale: initScale ?? 1, initPosition: zoomOffset, // initPosition: , // onPositionUpdate: onPositionUpdate, onScaleUpdate: onScaleUpdate, // zoomSensibility: 0.5, ); }, ); }, )); } } // 试卷绘制 class ExamPaperDrawing extends StatefulHookConsumerWidget { String imgUrl; bool homework; BoxDecoration? decoration; AnnotationGraffitiSwitch graffitiSwitch; List? points; List? pointsPureData; ValueNotifier> imageLoaded; GlobalKey globalKey; // Function(String) imageCall; ExamPaperDrawing({ required this.imgUrl, required this.homework, required this.points, required this.pointsPureData, required this.graffitiSwitch, required this.globalKey, required this.imageLoaded, this.decoration, Key? key, }) : super(key: key); @override _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); } class _ExamPaperDrawingState extends ConsumerState with EventBusMixin { late Future _future; // 考试试卷 // 用于记录手指位置的变量 late List points; late List pointsPureData; // 用于记录绘图结果的变量 bool _isEraserPressed = false; // 橡皮擦按下 Offset? _eraserPosition; // 按下位置 Offset? globalPosition = null; // 是否正在绘制 Future loadImage(String url) async { try { Map map = widget.imageLoaded.value; ui.Image? image = map[url]; if (image != null) { return image; } final httpClient = HttpClient(); final request = await httpClient.getUrl(Uri.parse(url)); final response = await request.close(); final bytes = await consolidateHttpClientResponseBytes(response); final codec = await ui.instantiateImageCodec(bytes); final frame = await codec.getNextFrame(); ui.Image theImage = frame.image; map[url] = theImage; return theImage; } catch (e) { print('请求图片报错:${e.toString()}'); } return null; } // void _onPointerDown(DragDownDetails details) { // if (widget.graffitiSwitch.openEraser) { // _eraserPosition = (context.findRenderObject() as RenderBox).globalToLocal(details.globalPosition); // _isEraserPressed = true; // toUpState(setState, ()=>{}, mounted); // } // } @override void initState() { points = widget.points ?? []; pointsPureData = widget.pointsPureData ?? []; print('图片地址:${widget.imgUrl}'); _future = loadImage(widget.imgUrl); // 事件总线监听 eventOn(callback: (BottomAnnotationSwitchCleanall item) { if (item.previousStep) { if (points.isEmpty) { ToastUtils.showInfo('批注已清空'); return; } var index = pointsPureData.toList().lastIndexOf(null); if (index != -1) { if (index + 1 == pointsPureData.length) { pointsPureData = pointsPureData.sublist(0, index); points.sublist(0, index); index = pointsPureData.toList().lastIndexOf(null); index == -1 ? -1 : index + 1; } if (index != -1) { pointsPureData = pointsPureData.sublist(0, index); points = points.sublist(0, index); toUpState(setState, () {}, mounted); } else { item.cleanAll = true; } } else { item.cleanAll = true; } } if (item.cleanAll) { pointsPureData.clear(); points.clear(); toUpState(setState, () {}, mounted); } if (item.uploadImage) { // 图片确认按钮,生成 if (pointsPureData.isEmpty) { ToastUtils.showInfo('请先批注再提交'); return; } // _saveImage().then((FileResult? res) { // if (res != null) { // widget.imageCall(res.url!); // } // }); } }); super.initState(); } @override void dispose() { super.dispose(); eventCancel(); } @override Widget build(BuildContext context) { return MyFutureBuilder.buildFutureBuilderOfSingleInstance( context, _future, (ui.Image? theImage) { if (theImage == null) return const Center(child: Text('图片加载错误')); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final double containerWidth = constraints.maxWidth; // 展示区域总宽度 final double containerHeight = constraints.maxHeight; // 展示区域总宽度 final double imageWidth = theImage.width.toDouble(); // 图片原始宽度 final double imageHeight = theImage.height.toDouble(); // 图片原始高度 final double widthRatio = containerWidth / imageWidth; // final double heightRatio = containerHeight / imageHeight; final double scale = widthRatio > heightRatio ? heightRatio : widthRatio; final double destWidth = imageWidth * scale; final double destHeight = imageHeight * scale; final bool homework = widget.homework; return GestureDetector( behavior: HitTestBehavior.opaque, onPanUpdate: (DragUpdateDetails details) { if (globalPosition != null) { // 预防双指同时作用于屏幕 double dx = globalPosition!.dx; double dy = globalPosition!.dy; double dxNew = details.globalPosition.dx; double dyNew = details.globalPosition.dy; if ((dxNew - dx).abs() > 22 || (dyNew - dy).abs() > 22) { return; } } globalPosition = details.globalPosition; try { if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { RenderBox renderBox = context.findRenderObject() as RenderBox; Offset localPosition = renderBox.globalToLocal(details.globalPosition); pointsPureData = List.from(pointsPureData)..add(localPosition); points = List.from(points) ..add(GestureRecording(eraser: widget.graffitiSwitch.openEraser, data: localPosition)); _eraserPosition = localPosition; _isEraserPressed = true; setState(() {}); } } catch (e) { toPrint(val: '进入报错'); } }, onPanEnd: (DragEndDetails details) { print('离开.............'); globalPosition = null; if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { pointsPureData.add(null); // 增加空点以分隔不同的线段 points.add(GestureRecording(eraser: widget.graffitiSwitch.openEraser)); _isEraserPressed = false; _eraserPosition = null; } }, child: RepaintBoundary( key: widget.globalKey, child: CustomPaint( // isComplex: true, // willChange: true, painter: DrawingPainter( image: theImage, points: points, isErasing: widget.graffitiSwitch.openEraser, destWidth: destWidth, destHeight: destHeight, imageWidth: imageWidth, imageHeight: imageHeight, homework: homework, containerWidth: containerWidth, containerHeight: containerHeight, ), // size: Size(homework ? containerWidth : destWidth, homework ? containerHeight : destHeight), size: Size(containerWidth, containerHeight), ), ), ); }, ); }, ); } } class DrawingPainter extends CustomPainter { final List points; final bool isErasing; final ui.Image image; final bool homework; final double containerWidth; final double containerHeight; double destWidth; double destHeight; final double imageWidth; final double imageHeight; // final Rect destRect; // final Rect srcRect; final bool openErasing; DrawingPainter({ required this.homework, required this.points, required this.isErasing, required this.image, required this.destWidth, required this.destHeight, required this.containerWidth, required this.containerHeight, required this.imageWidth, required this.imageHeight, }) : // destRect = Rect.fromLTWH(0, 0,destWidth,destHeight), // srcRect = Rect.fromLTWH(0, 0, imageWidth, imageHeight), openErasing = points.isNotEmpty && isErasing, super(); Paint paintBrush = Paint() ..color = Colors.red ..strokeCap = StrokeCap.round ..strokeWidth = 1.5; Paint eraser = Paint() ..blendMode = BlendMode.clear ..color = Colors.transparent ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round ..strokeWidth = 100; final emptyPaint = Paint(); final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; @override void paint(Canvas canvas, Size size) { double offsetX = (size.width - destWidth) / 2; double offsetY = (size.height - destHeight) / 2; if (destWidth < (size.width / 2)) { destWidth = size.width / 2; offsetX = (size.width - destWidth) / 2; } // final rect = Rect.fromCenter(center: center, width: destWidth, height: destHeight); Rect srcRect = Rect.fromLTRB(0, 0, image.width.toDouble(), image.height.toDouble()); // Rect destRect = Rect.fromLTRB(offsetX, offsetY, containerWidth, containerHeight); Rect destRect = Offset(offsetX, offsetY) & Size(destWidth, destHeight); canvas.drawImageRect(image, srcRect, destRect, emptyPaint); // canvas.drawImage(image, Offset.zero, emptyPaint); if (points.isNotEmpty) { // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 canvas.drawColor(Colors.transparent, BlendMode.clear); } for (int i = 0; i < points.length - 1; i++) { GestureRecording item = points[i]; GestureRecording nextItem = points[i + 1]; Offset? offsetData = item.data; Offset? nextOffsetData = nextItem.data; if (offsetData != null && nextOffsetData != null) { canvas.drawLine(offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); } } // 恢复画布状态. if (points.isNotEmpty) canvas.restore(); } // @override // bool shouldRepaint(DrawingPainter oldDelegate) { // List thePoints = oldDelegate.points; // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; // return thePoints != points; // } @override bool shouldRepaint(DrawingPainter oldDelegate) => true; } /** * 手势记录 */ 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), ], ), ); }, ); // return Container( // width: width, // height: height, // color: Colors.red, // alignment: Alignment.center, // child: tempFile != null // ? Image.file( // tempFile, // fit: BoxFit.contain, // width: double.infinity, // height: double.infinity, // ) // : CachedNetworkImage( // key: _useImgRefsh.imageKey.value, // fit: BoxFit.contain, // width: double.infinity, // // height: double.infinity, // imageUrl: imageUrl, // placeholder: (context, url) => // Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), // // imageBuilder: (context, imageProvider) => Container( // // decoration: BoxDecoration( // // image: DecorationImage( // // image: imageProvider, // // fit: BoxFit.fitWidth, // // // colorFilter: ColorFilter.mode(Colors.red, BlendMode.colorBurn), // // ), // // ), // // ), // 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 _useSwitch.showZoomImg.value ? /** Scrollbar( // thumbVisibility: true, thumbVisibility: true, controller: _useScrollController, child: SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, scrollDirection: Axis.vertical, // 设置垂直滚动 child: Transform.scale( scale: 0.4, alignment: Alignment.topCenter, child: $MyCachedNetworkImage( imageUrl: imageUrl, tempFile: _useSwitch.temFile.value, width: containerWidth, height: containerHeight, ), ), ), )*/ /** */ $MyCachedNetworkImage( imageUrl: imageUrl, tempFile: _useSwitch.temFile.value, width: containerWidth, height: containerHeight, imageBuilder: imageBuilder, ) : ExamPaperDrawing( imgUrl: imageUrl, graffitiSwitch: graffitiSwitch, points: _useSwitch.points.value, pointsPureData: _useSwitch.pointsPureData.value, decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)), globalKey: theglobalKey, key: examGlobalKey, imageLoaded: _useSwitch.imageLoaded, homework: homework, // imageCall: (String str) => widget.imageCall(str, currentIndex), ); } 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?.points; 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; } }