You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Syncfusion Flutter PDF包在PDF点击位置精准添加圆形

解决Flutter中Syncfusion PDF点击位置与绘制圆形位置不匹配问题

你遇到的问题核心是屏幕点击坐标和PDF文档内部坐标不匹配:直接使用globalPosition获取的是屏幕像素位置,没有考虑PDFViewer的缩放比例、滚动偏移以及PDF页面的实际尺寸,导致绘制的圆形位置偏差。


核心解决思路

  1. 使用Syncfusion内置坐标转换:SfPdfViewer提供的convertToPagePoint方法可自动将屏幕坐标转换为PDF页面的内部坐标,处理缩放、滚动等影响因素。
  2. 调整圆形绘制原点: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:24:58