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

如何在Flutter应用中实现图纸标注工具及版本控制功能?

在Flutter中实现图纸标注工具与版本控制方案

一、标注工具实现

1. 文本评论添加

针对PDF和图片两种格式,核心思路是先捕捉用户点击位置输入评论,再通过自定义绘制将评论叠加到视图,最终嵌入文件本身:

  • 视图层:用photo_view(图片)或flutter_pdfview(PDF)实现缩放查看,叠加CustomPaint层绘制评论标记和文本。
  • 交互逻辑:监听图纸区域的点击事件,弹出输入框获取评论内容,记录评论的坐标、文本、样式信息。
  • 保存到文件:
    • 图片:使用image库加载原图,在Image对象上绘制文本后保存为新文件。
    • PDF:借助printing和pdf库,将评论作为文本注释添加到对应PDF页面,生成新版本PDF。

核心绘制代码示例:

class MarkupPainter extends CustomPainter {
  final List<TextMarkup> textMarkups;

  MarkupPainter(this.textMarkups);

  @override
  void paint(Canvas canvas, Size size) {
    final textStyle = TextStyle(color: Colors.red, fontSize: 16);
    final textPainter = TextPainter(textDirection: TextDirection.ltr);

    for (var markup in textMarkups) {
      // 绘制评论标记点
      canvas.drawCircle(Offset(markup.x, markup.y), 4, Paint()..color = Colors.red);
      // 绘制评论文本
      textPainter.text = TextSpan(text: markup.content, style: textStyle);
      textPainter.layout();
      textPainter.paint(canvas, Offset(markup.x + 8, markup.y - textPainter.height / 2));
    }
  }

  @override
  bool shouldRepaint(covariant MarkupPainter oldDelegate) => oldDelegate.textMarkups != textMarkups;
}

class TextMarkup {
  final String content;
  final double x;
  final double y;

  TextMarkup(this.content, this.x, this.y);
}

2. 区域高亮

实现半透明区域高亮的核心是捕捉用户框选的区域,并用半透明画笔绘制:

  • 交互逻辑:监听触摸的按下、拖动事件,计算选中的矩形区域(或不规则路径),存入高亮列表。
  • 绘制逻辑:在CustomPainter中遍历高亮区域,使用带透明度的画笔绘制(比如Colors.yellow.withOpacity(0.3))。
  • 保存到文件:同文本评论,图片直接叠加绘制,PDF通过AnnotatedRegion添加高亮注释。

核心绘制片段:

// 在MarkupPainter中添加高亮绘制
for (var highlight in highlights) {
  canvas.drawRect(highlight.rect, Paint()..color = Colors.yellow.withOpacity(0.3));
}

3. 形状绘制(矩形、圆形、自由线条)

根据不同工具类型处理触摸事件,记录绘制路径或形状参数:

  • 自由线条:监听触摸的开始、更新、结束事件,收集触摸点生成Path,存入路径列表。
  • 矩形/圆形:触摸按下记录起点,拖动时计算当前矩形范围或圆心半径,抬起时将最终形状存入列表。
  • 绘制逻辑:在CustomPainter中分别绘制路径、矩形、圆形。

核心触摸处理代码:

enum ToolType { rectangle, circle, freehand }
ToolType _currentTool = ToolType.freehand;

List<Path> _freehandPaths = [];
List<Rect> _rects = [];
List<CircleMarkup> _circles = [];
Offset? _startPoint;
Rect? _tempRect;
CircleMarkup? _tempCircle;

void _onPanStart(DragStartDetails details) {
  _startPoint = details.localPosition;
  if (_currentTool == ToolType.freehand) {
    _freehandPaths.add(Path()..moveTo(_startPoint!.dx, _startPoint!.dy));
  }
}

void _onPanUpdate(DragUpdateDetails details) {
  final currentPos = details.localPosition;
  switch (_currentTool) {
    case ToolType.rectangle:
      _tempRect = Rect.fromPoints(_startPoint!, currentPos);
      break;
    case ToolType.circle:
      final radius = (_startPoint! - currentPos).distance;
      _tempCircle = CircleMarkup(_startPoint!, radius);
      break;
    case ToolType.freehand:
      _freehandPaths.last.lineTo(currentPos.dx, currentPos.dy);
      break;
  }
  setState(() {});
}

void _onPanEnd(DragEndDetails details) {
  if (_currentTool == ToolType.rectangle && _tempRect != null) {
    _rects.add(_tempRect!);
    _tempRect = null;
  } else if (_currentTool == ToolType.circle && _tempCircle != null) {
    _circles.add(_tempCircle!);
    _tempCircle = null;
  }
  _startPoint = null;
  setState(() {});
}

class CircleMarkup {
  final Offset center;
  final double radius;

  CircleMarkup(this.center, this.radius);
}

二、版本控制实现

1. 文件历史维护

通过本地数据库记录版本元数据,同时存储每个版本的文件:

  • 版本元数据模型:记录原始文件名、版本文件名、修改时间、变更描述、版本号。
  • 存储方案:用sqflite或hive搭建本地数据库,文件存储在应用的getApplicationDocumentsDirectory()目录下,命名规则为[原始文件名]_v[版本号].[后缀]。
  • 触发时机:每次保存标注后,复制当前文件为新版本,同时插入一条版本记录到数据库。

版本模型示例:

class FileVersion {
  final int id;
  final String originalFileName;
  final String versionFilePath;
  final DateTime timestamp;
  final String changeDesc;

  FileVersion({
    required this.id,
    required this.originalFileName,
    required this.versionFilePath,
    required this.timestamp,
    required this.changeDesc,
  });
}

2. 版本回滚

用户选择历史版本后,替换当前活跃文件并加载对应标注:

  • 流程:从数据库获取版本列表,用户选中后,将对应版本的文件复制为当前使用的文件;如果标注是单独存储的JSON文件,同步加载对应版本的标注数据。
  • 注意:如果标注已嵌入文件本身,直接替换文件即可;若标注与文件分离,需确保版本记录关联对应标注文件。

关键优化点

  • 大文件处理:PDF使用分页加载,图片使用分块加载或photo_view的内存优化;缩放依赖第三方库的内置功能,避免自定义缩放导致内存溢出。
  • 性能优化:用RepaintBoundary包裹绘制区域,减少重绘范围;缓存已绘制的标注层,避免频繁重新绘制。
  • 标注嵌入文件:确保最终保存的文件包含所有标注,而非仅在视图层显示,保证文件可独立查看。

内容的提问来源于stack exchange,提问作者Bakri Alkhateeb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 13:14:55