Flutter Svg转Path对象,path.getBounds()获取测量信息不准问题记录

问题描述

今天有群友询问一个问题,就是想把svg转换的path对象缩放绘制在一个边框容器内,但是无论如何计算偏移坐标和缩放都对不准位置和大小,一起看了看,感觉代码逻辑没问题,但是还是差点。

于是乎,因为想到自己可能有类似需求,就让群友把代码发我看了看。果然怎么改都不行。然后发现端倪,使用getBounds获取到的测量信息跟实际绘制出的路径,视觉上明显对不上。

然后把svg的path中的各点都绘制出来。果然发现问题所在,使用getBounds测量出来的连同曲线控制点也给算上了,这是用到的path:

arduino 复制代码
"M 288.0517, 4.0782 C420.9688, 1.9839 499.5736, 71.3123 537.7337, 164.3545 557.7525, 213.1645 561.2316, 294.7823 540.9246, 346.958 535.6071, 358.652 530.2881, 370.3494 524.9705, 382.0433 520.1162, 388.897 506.8675, 390.4646 499.4438, 394.8017 486.4159, 403.838 473.3842, 412.8769 460.3562, 421.9131 447.2893, 432.3373 436.3263, 450.3531 428.4479, 465.7699 426.0551, 471.6168 423.6616, 477.4656 421.2686, 483.3125 410.2451, 497.7952 354.2471, 507.8667 330.3301, 512.0188 287.0907, 519.5253 240.4923, 508.3152 211.4718, 496.8683 104.4496, 454.6541 1.4113, 322.4794 62.3006, 167.5441 90.1993, 96.5551 142.6207, 46.1894 213.865, 18.4313 230.0834, 14.4447 246.3066, 10.4569 262.5249, 6.4703 271.033, 5.673 279.5436, 4.8755 288.0517, 4.0782 Z"

从图上我们就能看出来,空白区域的那部分宽或高,跟他上面截图的问题差不多,于是乎我直接推断就是这个问题导致到。

解决方案

知道是因为曲线弯曲控制点也被计算在内导致的,那么我们是不是可以考虑直接排除这些曲线控制点,然后取剩余点的的最小x、最小y、最大x、最大y,最终返回一个真实绘制路径测量的Rect。怎么实现呢,直接交给AI吧,哈哈哈哈。工具就是给人服务的,AI实现出来的就是你实现出来的,不要有什么自己过于依赖AI的愧疚感。通过对路径进行采样,来自己确定哪些点参与路径绘制了,这方法虽然当采样步长小的时候性能上不行,但是好在能精度更高的解决问题,先实现再等待更好的方案出现

dart 复制代码
/// 对路径进行采样,获取路径上的点集合
List<Offset> samplePathPoints(String pathData, {double step = 1.0}) {
  Path path = parseSvgPathData(pathData);
  List<Offset> points = [];
  // 获取路径的所有测量信息
  List<PathMetric> metrics = path.computeMetrics().toList();
  for (PathMetric metric in metrics) {
    // 沿着路径以指定步长采样点
    for (double distance = 0.0; distance <= metric.length; distance += step) {
      Tangent? tangent = metric.getTangentForOffset(distance);
      if (tangent != null) {
        points.add(tangent.position);
      }
    }
    // 确保包含路径的终点
    Tangent? endPoint = metric.getTangentForOffset(metric.length);
    if (endPoint != null && !points.contains(endPoint.position)) {
      points.add(endPoint.position);
    }
  }
  return points;
}

然后再看一下,是不是测量的非常准确了,OK解决了。

最后附上完整核心代码

dart 复制代码
import 'dart:math';
import 'dart:ui' show Path, Rect, Offset, PathMetric, Tangent;

import 'package:path_drawing/path_drawing.dart';

/// Path类的扩展,提供了额外的实用方法
class PathUtils{
  static Rect getBoundsReal(String svgPath){
    List<Offset> sampledPoints = samplePathPoints(svgPath);
    return Rect.fromPoints(
      _getLeftTopPointFromKeyPoints(sampledPoints),
      _getRightBottomPointFromKeyPoints(sampledPoints),
    );
  }

  /// 过滤掉SVG路径中的弯曲控制点,只保留关键点(起始点和终点)
  /// 然后获取最左侧点的x坐标和最上方点的y坐标组成新点
  static Offset _getLeftTopPointFromKeyPoints(List<Offset> sampledPoints) {
    if (sampledPoints.isEmpty) {
      return Offset.zero;
    }

    // 找到最左侧点的x坐标(最小x值)
    double leftMostX = sampledPoints.map((point) => point.dx).reduce(min);

    // 找到最上方点的y坐标(最小y值)
    double topMostY = sampledPoints.map((point) => point.dy).reduce(min);

    return Offset(leftMostX, topMostY);
  }

  /// 获取最右侧点的x坐标和最下方点的y坐标组成新点
  static Offset _getRightBottomPointFromKeyPoints(List<Offset> sampledPoints) {
    if (sampledPoints.isEmpty) {
      return Offset.zero;
    }

    // 找到最右侧点的x坐标(最大x值)
    double rightMostX = sampledPoints.map((point) => point.dx).reduce(max);

    // 找到最下方点的y坐标(最大y值)
    double bottomMostY = sampledPoints.map((point) => point.dy).reduce(max);

    return Offset(rightMostX, bottomMostY);
  }

  /// 对路径进行采样,获取路径上的点集合
  static List<Offset> samplePathPoints(String svgPath, {double step = 1.0}) {
    Path path = parseSvgPathData(svgPath);
    List<Offset> points = [];
    // 获取路径的所有测量信息
    List<PathMetric> metrics = path.computeMetrics().toList();
    for (PathMetric metric in metrics) {
      // 沿着路径以指定步长采样点
      for (double distance = 0.0; distance <= metric.length; distance += step) {
        Tangent? tangent = metric.getTangentForOffset(distance);
        if (tangent != null) {
          points.add(tangent.position);
        }
      }
      // 确保包含路径的终点
      Tangent? endPoint = metric.getTangentForOffset(metric.length);
      if (endPoint != null && !points.contains(endPoint.position)) {
        points.add(endPoint.position);
      }
    }
    return points;
  }
}

用上面getBoundsReal来代替path.getBounds。最后Svg路径转Path我用的是path_drawing

📚 结尾

开拓思路,大胆用AI尝试,反正不是烧的自己的脑细胞,努力成为AI人柱力

相关推荐
坚果派·白晓明1 小时前
Windows 11 OpenHarmony 版 Flutter 开发环境搭建完整指南
windows·flutter·开源鸿蒙·鸿蒙跨平台应用
音浪豆豆_Rachel1 小时前
Flutter跨平台通信的实战演练:复杂数据结构与单元测试在鸿蒙生态中的完美实现
数据结构·flutter·单元测试·harmonyos
音浪豆豆_Rachel2 小时前
Flutter跨平台通信的类型安全艺术:枚举与复杂对象在鸿蒙生态中的映射与序列化
flutter·harmonyos
昼-枕2 小时前
【鸿蒙Flutter入门】10分钟快速上手开发天气应用
flutter·华为·harmonyos
kirk_wang3 小时前
Flutter `shared_preferences` 三方库在 OpenHarmony 平台的适配实践
flutter·移动开发·跨平台·arkts·鸿蒙
音浪豆豆_Rachel3 小时前
Flutter鸿蒙文件选择器内核解析:从Dart调用到ArkTS系统级对话
flutter·harmonyos
音浪豆豆_Rachel3 小时前
Flutter鸿蒙文件选择器实现层解析:消息通道、协议转换与数据处理
flutter·华为·harmonyos
音浪豆豆_Rachel3 小时前
Flutter鸿蒙文件选择器入口解析:插件生命周期与平台绑定
flutter·harmonyos
消失的旧时光-19433 小时前
401 刷新 Token 的队列版(请求挂起排队 + 刷新后统一重放/统一失败)
flutter·dio
消失的旧时光-19433 小时前
Repository 层如何无缝接入本地缓存 / 数据库
数据库·flutter·缓存