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人柱力

相关推荐
提子拌饭1331 小时前
风息时钟:鸿蒙Flutter 实现的自然风格时钟应用
flutter·华为·架构·开源·harmonyos
浮芷.4 小时前
Flutter 框架跨平台鸿蒙开发 - AR动物互动应用
flutter·ar·harmonyos
加农炮手Jinx6 小时前
Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构
flutter·harmonyos·鸿蒙·openharmony
王码码20356 小时前
Flutter 三方库 appstream 的鸿蒙化适配指南 - 驾驭 Linux 生态元数据规范,打造高性能、标准化、国际化的 OpenHarmony 桌面应用商店分发基石
flutter·harmonyos·鸿蒙·openharmony
见山是山-见水是水6 小时前
Flutter 框架跨平台鸿蒙开发 - AR植物养护助手
flutter·华为·ar·harmonyos
autumn20056 小时前
Flutter 框架跨平台鸿蒙开发 - 历史人物对话
服务器·flutter·华为·harmonyos
autumn20057 小时前
Flutter 框架跨平台鸿蒙开发 - 社区闲置循环
flutter·华为·harmonyos
浮芷.8 小时前
Flutter 框架跨平台鸿蒙开发 - 家庭健康监测云应用
科技·flutter·华为·harmonyos·鸿蒙
世人万千丶8 小时前
Flutter 框架跨平台鸿蒙开发 - 宠物语言翻译器应用
学习·flutter·华为·开源·harmonyos·鸿蒙
见山是山-见水是水9 小时前
Flutter 框架跨平台鸿蒙开发 - NPC模拟器
flutter·华为·harmonyos