【Flutter 绘制番外】svg 文件与绘制 (上)

2022-03-18 15:19:00 浏览数 (1)

前言

对一些有趣的绘制 技能知识, 我会通过 [番外篇] 的形式加入《Flutter 绘制指南 - 妙笔生花》小册中,一方面保证小册的“与时俱进”“活力”。另一方面,是为了让一些重要的知识有个 好的归宿。本文源码可以看这里。 另外一个好消息: 《Flutter 绘制指南 - 妙笔生花》小册源码 idraw 已经完成了 空安全 的转化。

一、对 svg 的认识
1. 初见

通过 F12 可以看到掘金的 logo 是一个 svg ,可以将它作为文件下载。

打开后可以看出其中有很多不明所以的字符,可以确定的是:这些字符决定了 logo 的显示。至于这些字符为什么可以控制显示,又如何控制显示,对于初见者并不能理解。

2. 试探

AdroidStudio 中可以实时显示 svg 文件的表现效果,如下将一段 path 注释掉,可以看出 少了一块。

再注释掉一个 path ,可以看到又少了一块。所以可以看出,每个 path 块都表示一部分的路径。

二、直线路径操作符
1. 水平和竖直 绝对 路径: HV

如下是 M0,0 H50 V50 H0 的效果:

代码语言:javascript复制
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M0,0 H50 V50 H0" fill="#FFFFFF"/>
</svg>

M0,0 表示:从起点移至 0,0H50 表示:水平移动 横坐标 50 处。 V50 表示:竖直移动 纵坐标 50 处。 H0 表示:水平移动 横坐标 0 处。 最后路径会连到起点 0,0 ,另外 fill="#FFFFFF" 表示填充白色。

2.绝对移动 : M

如下橙色区域是 M20,0 H50 V50 H0 的效果,M20,0 表示路径起点移至 (20,0) 坐标。

4.直线绝对 路径:L

如下蓝色区域是 M28,0 L43,0 12,32 4,25 的效果,只要知道点的坐标就可以通过 L 符号,将点依次拼接形成路径。

只要知道图形所有的点位坐标,就可以形成路径。将多段路径合在一起,就可以来显示期望的图案,比如下面的 Flutter 图标。

代码语言:javascript复制
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M0,0 H50 V50 H0" fill="#FFFFFF" />
    <path d="M28,2 L43,2 12,32 4,25" fill="#3AD0FF" />
    <path d="M16,36 L30,48 44,48 23,29" fill="#00559E" />
    <path d="M16,36 L28,24 42,24 24,43" fill="#3AD0FF" />
</svg>

仔细看一下,就可以发现,这么复杂的字符,也只是 M/H/V/L 四个操作符的拼接而已。另外注意一下,逗号 , 和空格 可以互换。一般的 svg 文件都是由 设计软件 生成的,所以都是空格,一般不具有可读性。

其实对于 Flutter 绘制而言,最重要的是路径 Path 的形成,那么既然 svg 文件里有路径信息,是不是意味着我们可以提取坐标、生成路径,然后进行绘制呢?废话不多说,一起来试验一下。

三、svg 直线型操作符转化为 Path 对象
1. 如何对 svg 路径进行解析

现在的问题在于如何将 svg 路径解析处我们需要的信息,对一字符串的处理,自然是非 正则 莫属了。只要写出一条何时的正则,进行匹配即可。

代码语言:javascript复制
<path d="M17.5865 17.3955H17.5902L28.5163 8.77432L25.5528 6.39453L17.5902 12.6808H17.5865L17.5828 12.6845L9.62018 6.40201L6.6604 8.78181L17.5828 17.3992L17.5865 17.3955Z" fill="#1E80FF"/>

在后期会发布一个关于 正则的小册 ,其中会通过实现如下的正则校验的应用,来让大家更有趣地认识正则。目前文件中只有 M,H,V,L 四个指令,通过下面的命令就可以匹配每段指令的信息。

通过匹配后,我们就可以获取其中必要的信息,如下所示:

2. 与 Flutter 绘制的衔接

如下方法是通过解析一条 svg 路径,形成 FlutterPath 的过程。注意目前只有 M,H,V,L,Z 四个指令,其他 svg 指令在后面会继续完善。通过绘制形成的路径就能显示出来了:

代码语言:javascript复制
Color color = const Color(0xff1E80FF);
canvas.drawPath(formPathFromSvgOp(src), paint..color=color);
代码语言:javascript复制
Path formPathFromSvgOp(String src) {
  Path path = Path();
  RegExp regExp = RegExp(r'[M,H,V,L](((d .d )|d)([ ,])?) |Z');
  List<RegExpMatch> results = regExp.allMatches(src).toList();
  double lastX = 0;
  double lastY = 0;
  results.forEach((RegExpMatch element) {
    String? op = element.group(0);
    if (op != null) {
      if (op.startsWith("M")) {
        List<String> pos = op.substring(1).split(RegExp(r'[, ]'));
        double dx = num.parse(pos[0]).toDouble();
        double dy = num.parse(pos[1]).toDouble();
        path.moveTo(dx, dy);
        lastX = dx;
        lastY = dy;
      }
      if (op.startsWith("L")) {
        List<String> pos = op.substring(1).split(RegExp(r'[, ]'));
        for (int i = 0; i < pos.length; i  = 2) {
          double dx = num.parse(pos[i]).toDouble();
          double dy = num.parse(pos[i   1]).toDouble();
          path.lineTo(dx, dy);
          lastX = dx;
          lastY = dy;
        }
      }
      if (op.startsWith("H")) {
        print(op.length);
        List<String> pos = op.substring(1).trim().split(RegExp(r'[, ]'));
        for (int i = 0; i < pos.length; i   ) {
          print('${pos[i]}');
          double dx = num.parse(pos[i]).toDouble();
          double dy = lastY;
          path.lineTo(dx, dy);
          lastX = dx;
        }
      }
      if (op.startsWith("V")) {
        List<String> pos = op.substring(1).trim().split(RegExp(r'[, ]'));
        for (int i = 0; i < pos.length; i   ) {
          double dx = lastX;
          double dy = num.parse(pos[i]).toDouble();
          path.lineTo(dx, dy);
          lastY = dy;
        }
      }
      if (op.startsWith("Z")) {
       path.close();
      }
    }
  });
  return path;
}

这样只要把三个路径画出来,就可以形成掘金的 logo ,如下所示:

这样 svg 文件通过 path 进行分割,遍历形成路径即可。注意一下,效果上来看,文字部分似乎不是很好,因为这里 只解析了 M、H、L、V 四个指令。仔细来看,这个文件中还有 C 指令用于形成贝塞尔曲线,这个指令在下一篇进行讲解,使用目前效果不好是正常的。下一章对 C 解析后就可以完美了。

3.颜色的解析

同样通过正则表达式匹配每条路径中的颜色信息,如下所示:

由于每条 svg 路径都有路径信息和颜色信息,使用可以定义一个 SVGPathResult 对象进行维护。

代码语言:javascript复制
class SVGPathResult{
  final Color color;
  final Path path;

  SVGPathResult({required this.color, required this.path});
}

对颜色的解析逻辑如下:

代码语言:javascript复制
  SVGPathResult formPathFromSvgOp(String src) {
    Color resultColor = Colors.black;
    RegExp color = RegExp(r'(?<=#)(.*)(?=")');
    List<RegExpMatch> colorResult = color.allMatches(src).toList();
    if(colorResult.length>0){
      String? colorStr = colorResult[0].group(0);
      if(colorStr!=null){
        print(colorStr);
        resultColor = Color(int.parse(colorStr, radix: 16)   0xFF000000);
      }
    }

这样,之前写的 Flutter 图标的 svg 就可以解析涂色的,效果如下:

本文主要介绍了 H、V、L 三个绝对直线路径的使用以及正则解析,用于 FlutterPath 对象的形成。svg 中还有一些其他比较重要的操作符,我们将在接下来的文章中介绍,并对其进行解析。所以 千万别以为这点解析逻辑能解析任何 svg 文件 ,后续还需很多细节的完善。那本文就到这里,谢谢观看~

0 人点赞