如何用Syncfusion Flutter PDF包在PDF点击位置精准添加圆形
解决Flutter中Syncfusion PDF点击位置与绘制圆形位置不匹配问题
你遇到的问题核心是屏幕点击坐标和PDF文档内部坐标不匹配:直接使用globalPosition获取的是屏幕像素位置,没有考虑PDFViewer的缩放比例、滚动偏移以及PDF页面的实际尺寸,导致绘制的圆形位置偏差。
核心解决思路
- 使用Syncfusion内置坐标转换:SfPdfViewer提供的
convertToPagePoint方法可自动将屏幕坐标转换为PDF页面的内部坐标,处理缩放、滚动等影响因素。 - 调整圆形绘制原点:
drawPie的Rect.fromLTWH基于矩形左上角坐标,而点击位置是圆形圆心,需要将转换后的坐标减去圆形半径(25,对应直径50),让圆心与点击位置重合。
修改后的关键代码实现
1. 调整AnnotationPage组件(接收PDFViewer的全局Key)
class AnnotationPage extends StatefulWidget { final GlobalKey<SfPdfViewerState> pdfViewerKey; const AnnotationPage({Key? key, required this.pdfViewerKey}) : super(key: key); @override _AnnotationPageState createState() => _AnnotationPageState(); } class _AnnotationPageState extends State<AnnotationPage> { // ... 原有变量保持不变 @override Widget build(BuildContext context) { return Positioned( left: x, top: y, child: Stack( children: [ GestureDetector( onScaleStart: (details) { startPoint = details.focalPoint; }, onTapUp: (TapUpDetails details) { // 获取PDFViewer状态并转换坐标 final pdfViewerState = widget.pdfViewerKey.currentState; if (pdfViewerState != null) { // 将相对于PDFViewer的局部坐标转换为第0页的文档坐标 final pagePoint = pdfViewerState.convertToPagePoint( details.localPosition, 0, ); // 计算圆形左上角坐标:圆心位置减去半径25 dx = pagePoint.dx - 25; dy = pagePoint.dy - 25; print('转换后的PDF文档坐标:dx=$dx, dy=$dy'); } }, onScaleUpdate: (details) { // ... 原有缩放逻辑保持不变 }, child: const Icon(Icons.circle_outlined, size: 50), ), ], ), ); } }
2. 在HomePage中传递PDFViewer的全局Key
// HomePage的Stack组件中修改AnnotationPage的调用 Stack( children: [ SfPdfViewer.memory( documentBytes!, key: _pdfViewerKey, controller: _pdfViewerController, onDocumentLoaded: (PdfDocumentLoadedDetails details) { _pdfViewerController.jumpTo(xOffset: xOffset, yOffset: yOffset); }, ), AnnotationPage(pdfViewerKey: _pdfViewerKey), // 传递全局Key ], )
3. 确保全局变量正确声明(global.dart)
import 'dart:typed_data'; Uint8List? documentBytes; double dx = 0.0; double dy = 0.0;
完整修改后的代码
import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; Uint8List? documentBytes; double dx = 0.0; double dy = 0.0; void main() { runApp(const MaterialApp( home: MyApp(), )); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override _HomePage createState() => _HomePage(); } class _HomePage extends State<HomePage> { final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey(); final PdfViewerController _pdfViewerController = PdfViewerController(); OverlayEntry? _overlayEntry; double yOffset = 0.0; double xOffset = 0.0; final Color _contextMenuColor = const Color(0xFFFFFFFF); final Color _textColor = const Color(0xFF000000); @override void initState() { getPdfBytes(); super.initState(); } PdfPolygonAnnotation? polygonAnnotation; double x = 0.0; double y = 0.0; double fontSize = 26.0; double scalingFactor = 1.5; Offset startPoint = Offset.zero; TextEditingController editableText = TextEditingController(); File? file; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Syncfusion Flutter PDF Viewer'), actions: [ GestureDetector( onTap: () async { final PdfDocument document = PdfDocument(inputBytes: documentBytes); document.pages[0].graphics.drawPie( Rect.fromLTWH(dx, dy, 50, 50), 0, 360, brush: PdfBrushes.green); final List<int> bytes = document.saveSync(); setState(() { documentBytes = Uint8List.fromList(bytes); }); document.dispose(); }, child: const Icon(Icons.save)), const SizedBox(width: 40), ], ), body: documentBytes != null ? Stack( children: [ SfPdfViewer.memory( documentBytes!, key: _pdfViewerKey, controller: _pdfViewerController, onDocumentLoaded: (PdfDocumentLoadedDetails details) { _pdfViewerController.jumpTo( xOffset: xOffset, yOffset: yOffset); }, ), AnnotationPage(pdfViewerKey: _pdfViewerKey), ], ) : Container(), ); } void getPdfBytes() async { documentBytes = await http.readBytes(Uri.parse('https://pdfobject.com/pdf/sample.pdf')); setState(() {}); } } class AnnotationPage extends StatefulWidget { final GlobalKey<SfPdfViewerState> pdfViewerKey; const AnnotationPage({Key? key, required this.pdfViewerKey}) : super(key: key); @override _AnnotationPageState createState() => _AnnotationPageState(); } class _AnnotationPageState extends State<AnnotationPage> { double x = 0.0; double y = 0.0; double fontSize = 26.0; double scalingFactor = 1.5; Offset startPoint = Offset.zero; TextEditingController editableText = TextEditingController(); File? file; @override void initState() { super.initState(); editableText.text = 'Add Text Here'; } @override Widget build(BuildContext context) { return Positioned( left: x, top: y, child: Stack( children: [ GestureDetector( onScaleStart: (details) { startPoint = details.focalPoint; }, onTapUp: (TapUpDetails details) { final pdfViewerState = widget.pdfViewerKey.currentState; if (pdfViewerState != null) { final pagePoint = pdfViewerState.convertToPagePoint( details.localPosition, 0, ); dx = pagePoint.dx - 25; dy = pagePoint.dy - 25; } }, onScaleUpdate: (details) { setState(() { x += (details.focalPoint.dx - startPoint.dx) * scalingFactor; y += (details.focalPoint.dy - startPoint.dy) * scalingFactor; fontSize = (fontSize * details.scale).clamp(16.0, 120.0); startPoint = details.focalPoint; }); }, child: const Icon(Icons.circle_outlined, size: 50), ), ], ), ); } }
修改后,点击PDF上的任意位置,保存后的圆形会精准对应点击位置,因为坐标已经经过正确的屏幕→PDF文档转换,且调整了圆形绘制的原点偏移。
内容的提问来源于stack exchange,提问作者Akashptl
相关产品推荐
相关产品推荐

