likes
comments
collection
share

Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

作者站长头像
站长
· 阅读数 2

[》跳过拾光记忆]

拾光记忆

简介: 针对 Flutter 项目资产管理的脚本服务。Fam 具有以下特点: 支持多种平台以及各平台无差异化、界面美观、功能齐全、快捷方便。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 针对 Flutter 多手指检测以及手势触发其他手势也触发的问题。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 这是让开发者更深入的了解 Dart 的枚举以及相关使用和方法。 推荐: ⭐️⭐️⭐️

简介: 这是让开发者更加便捷的实现单选、多选功能,无需你对数据处理。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 这是让开发者更深入的了解 Flutter 的两种指针的介绍以及相关使用和方法。 推荐: ⭐️⭐️⭐️

简介: 这是让开发者更加便捷的实现多种样式的轨道滑块,比如:轨道渐变色、触控笔悬浮标等。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 这是让开发者更加便捷的查看日志,通过日志能够分析变量当前数值以及业务逻辑走势,同时也能作为日志收集日志文本。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 这是让开发者更加便捷的实现图像添加图片类型的水印和文字类型的水印,支持水印多种样式的设定。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: IImage 是 IDKit 的其中一个功能模块,它提供了许多方便的图像处理方法,例如:尺寸调整、相对某点的矩形位置、大小调整、图像缩放、背景去除、真实内容尺寸获取以及真实内容图像获取等等。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Matrix4 的 16 个参数的含义。 推荐: ⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Flutter 中所有颜色的模式以及对应颜色的一些便捷方法有和LAB 颜色的支持以及颜色差异提供算法。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Flutter 中如何实现图像的油漆桶、填充功能或者图像泛洪算法。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Flutter 之 IImage 库中如何实现图像的镜像功能以及实现原理的介绍。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Flutter 之 IImage 库中如何实现图像的旋转功能以及实现原理的介绍。图像旋转不组件旋转而是图像元数据旋转。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Flutter 之 IImage 库中如何实现图像的灰度或者去色功能以及实现原理的介绍。 推荐: ⭐️⭐️⭐️⭐️⭐️

简介: 该篇主要介绍 Flutter 之 IImage 库中如何实现图像反色功能以及实现原理的介绍。 推荐: ⭐️⭐️⭐️⭐️⭐️

[返回拾光记忆《]

一、简述

路径(Path)在图像绘制 (Canvas) 领域占有很大的地位。 你对各个开发语言的 Ptah 有多少的了解? 同时作为 Flutter 开发者,你又对 flutter 中的 Canvas-Path 又有多深的认知? 如果你感觉了解的还不是很多以及很深,这篇文章将带你进入 Path 深入了解之中。

二、Path 方法详解

1. Path 的创建

  • Path() 创建一个空的 Path 对象。它的核心代码是调用 _constructor() 方法进行构造,而 _constructor() 方法调用 Path::Create 方法进行生成的。而 Path::Create 方法是在 flutter/enginelib/ui/painting/path.cc 文件中。

    注意:在文件 path.ccPath 又被 typedef 定义为 CanvasPath 的别名。代码如下: typedef CanvasPath Path;

    Flutter 中代码调用方式如下:

    final Path path = Path();
    print(path);
    
  • factory Path.from(Path source) 该方法是通过对一个路径进行复制形成一个新的路径。它的核心代码是:

    factory Path.from(Path source) {
      final Path clonedPath = Path._();
      source._clone(clonedPath);
      return clonedPath;
    }
    

    上面代码中 _clone(Path x) 方法是将 source 路径信息拷贝到 clonePath 路径中。该方法的核心代码如下:

    void CanvasPath::clone(Dart_Handle path_handle) {
      fml::RefPtr<CanvasPath> path = Create(path_handle);
      // per Skia docs, this will create a fast copy
      // data is shared until the source path or dest path are mutated
      path->mutable_path() = this->path();
    }
    

    上面代码中的 Create(Dart_Handle wrapper)mutable_path()path() 这三个方法声明以及实现都在 lib/ui/painting/path.h 中。该代码是生成了 fml::RefPtr<CanvasPath> 类型的 path 指针,然后将 source 路径的 tracked_path_->path 赋值给新路径 pathtracked_path_->path

    方法 Path.from(Path source)Flutter 中调用的方法如下:

    class PathCustomPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        // source path
        final Path path = Path();
        path.moveTo(10, 60);
        path.lineTo(200, 60);
        // from 构建
        final Path pathFrom = Path.from(path);
        canvas.drawPath(
          pathFrom,
          Paint()
            ..color = Colors.red
            ..style = PaintingStyle.stroke
            ..strokeWidth = 12,
        );
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
    }
    

    代码运行视图效果:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

2. void moveTo(double x, double y)

  • 方法介绍

    该方法是一个实例方法,它是在给定坐标处 (x,y) 创建新的子路径。该方法是调用 Path::moveTo 方法,然后它调用 SkPathSkPath& SkPath::moveTo(SkScalar x, SkScalar y) 实现的,代码实现如下:

    SkPath& SkPath::moveTo(SkScalar x, SkScalar y) {
        SkDEBUGCODE(this->validate();)
        SkPathRef::Editor ed(&fPathRef);
        // remember our index
        fLastMoveToIndex = fPathRef->countPoints();
        ed.growForVerb(kMove_Verb)->set(x, y);
        return this->dirtyAfterEdit();
    }
    

    代码所在路径是 skia 库的 src/core/SkPath.cpp 文件中。

  • 方法实例

    FluttermoveTo(double x, double y) 方法的调用代码如下:

    class PathCustomPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        // moveTo 移动画笔起始点
        final Path path = Path();
        // move1
        path.moveTo(100, 100);
        path.lineTo(200, 100);
        // move2
        path.moveTo(100, 200);
        path.lineTo(200, 200);
        // draw
        canvas.drawPath(
          path,
          Paint()
            ..color = Colors.red
            ..style = PaintingStyle.stroke
            ..strokeWidth = 12,
        );
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
    }
    

    上面代码运行的视图效果如下:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

    从上图以及实现的代码可知,我们在 path 中使用了两次 moveTo 的方法以及在视图中 path 显示两条线段,所以 path 调用 moveTo 方法是在给定坐标处创建新的子路径。

3. void relativeMoveTo(double dx, double dy)

  • 方法介绍 该方法是在当前点的(dx,dy)偏移处创建新的路径。

    注意:该方法是以当前点为原点坐标,计算 (dx,dy) 的偏移。

    该方法底层是调用 Path::relativeMoveTo 方法, 而Path::relativeMoveTo 的实现如下:

      void CanvasPath::relativeMoveTo(double x, double y) {
        mutable_path().rMoveTo(SafeNarrow(x), SafeNarrow(y));
        resetVolatility();
      }
    

    在上边代码中又调用了SKPathSkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) 方法,该方法的实现如下:

    SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) {
        SkPoint pt = {0,0};
        int count = fPathRef->countPoints();
        if (count > 0) {
            if (fLastMoveToIndex >= 0) {
                pt = fPathRef->atPoint(count - 1);
            } else {
                pt = fPathRef->atPoint(~fLastMoveToIndex);
            }
        }
        return this->moveTo(pt.fX + x, pt.fY + y);
    }
    

    上面代码是先获取当前 Path 的最后一个 SkPoint 对象,然后调用 SKPathmoveTo 方法实现的。

  • 方法实例 在 FlutterrelativeMoveTo 方法的调用实例如下:

    class PathCustomPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        final Path path = Path();
        path.moveTo(10, 100);
        path.lineTo(100, 100);
        path.relativeMoveTo(100, 100);
        path.lineTo(200, 10);
        canvas.drawPath(
          path,
          Paint()
            ..style = PaintingStyle.stroke
            ..strokeWidth = 10
            ..color = Colors.red,
        );
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
    }
    

    上边代码运行实例效果如下:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

4. void lineTo(double x, double y)

  • 方法介绍 该方法是添加从当前点到 (dx,dy) 的直线。 该方法底层是调用 Path::lineTo 方法, Path::lineTo 方法实现如下:

    void CanvasPath::lineTo(double x, double y) {
      mutable_path().lineTo(SafeNarrow(x), SafeNarrow(y));
      resetVolatility();
    }
    

    上面方法又调用 SkPathSkPath::lineTo(SkScalar x, SkScalar y) 方法, 而 SkPath::lineTo(SkScalar x, SkScalar y) 方法的实现如下:

    SkPath& SkPath::lineTo(SkScalar x, SkScalar y) {
        SkDEBUGCODE(this->validate();)
        this->injectMoveToIfNeeded();
        SkPathRef::Editor ed(&fPathRef);
        ed.growForVerb(kLine_Verb)->set(x, y);
        return this->dirtyAfterEdit();
    }
    
  • 方法实例 在 Fluttervoid lineTo(double x, double y) 方法的调用实例如下:

    class PathCustomPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        final Path path = Path();
        path.moveTo(10, 10);
        path.lineTo(100, 10);
        path.moveTo(10, 30);
        path.lineTo(100, 30);
        path.lineTo(100, 60);
        canvas.drawPath(
          path,
          Paint()
            ..color = Colors.red
            ..style = PaintingStyle.stroke
            ..strokeWidth = 3,
        );
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
    }
    

    上面的方法运行效果如下:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

5. void relativeLineTo(double dx, double dy)

  • 方法介绍 该方法是: 在距当前点,给定偏移 (dx,dy) 处创建新的子路径。

    注意 : 该方法是以当前点为原点来计算偏移位置的。例如点前点是 (10,10) 而偏移点坐标为 (50,10) ,则绘制线段是一个斜线而不是水平线,该线的终点是 (60, 20)。

    该方法是调用 Path::relativeLineTo 方法 ,方法实现如下:

    void CanvasPath::relativeLineTo(double x, double y) {
      mutable_path().rLineTo(SafeNarrow(x), SafeNarrow(y));
      resetVolatility();
    }
    

    在上边代码中又调用了SKPathSkPath& SkPath::rLineTo(SkScalar x, SkScalar y) 方法,该方法的实现如下:

    SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) {
        this->injectMoveToIfNeeded();  // This can change the result of this->getLastPt().
        SkPoint pt;
        this->getLastPt(&pt);
        return this->lineTo(pt.fX + x, pt.fY + y);
    }
    

    上面代码是先获取当前 Path 的最后一个 SkPoint 对象,然后调用 SKPathmoveTo 方法实现的。

  • 方法实例 在 FlutterrelativeLineTo(double dx, double dy) 调用实例如下:

    class PathCustomPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        final Path path = Path();
        path.moveTo(50, 50);
        path.lineTo(100, 50);
        path.relativeLineTo(50, 100);
        path.relativeLineTo(50, 0);
        canvas.drawPath(
          path,
          Paint()
            ..style = PaintingStyle.stroke
            ..strokeWidth = 10
            ..color = Colors.red,
        );
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
    }
    

    上边方法运行效果视图如下:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

6. void quadraticBezierTo(double x1, double y1, double x2, double y2)

  • 方法介绍 该方法是使用控制点 (x1,y1) 添加从当前点到给定点 (x2,y2) 的二次贝塞尔曲线段。该方法是调用 Path::quadraticBezierTo 方法,这个方法的核心实现代码如下:

    void CanvasPath::quadraticBezierTo(double x1, double y1, double x2, double y2) {
      mutable_path().quadTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                            SafeNarrow(y2));
      resetVolatility();
    }
    

    上述方法有调用了 SkPathSkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) 方法, 这个方法的实现如下:

    SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
        SkDEBUGCODE(this->validate();)
        this->injectMoveToIfNeeded();
        SkPathRef::Editor ed(&fPathRef);
        SkPoint* pts = ed.growForVerb(kQuad_Verb);
        pts[0].set(x1, y1);
        pts[1].set(x2, y2);
        return this->dirtyAfterEdit();
    }
    
  • 方法实例 在 FlutterquadraticBezierTo 方法使用的实例如下:

    void quadraticBezierTo(Canvas canvas) {
      final Path path = Path();
      path.moveTo(100, 100);
      path.quadraticBezierTo(200, 200, 300, 100);
      path.moveTo(400, 100);
      path.quadraticBezierTo(500, 200, 600, 100);
      path.moveTo(250, 200);
      path.quadraticBezierTo(350, 300, 450, 200);
      canvas.drawPath(
        path,
        Paint()
          ..color = Colors.red
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2,
      );
    }
    

    上面代码运行的结果如下所示:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

    其中控制点(200,200)是过曲线的两点(100,100)、(300,100) 的切线相交的交点。图中手绘粗糙,凑合看。

7. void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)

  • 方法介绍 该方法是在当前点偏移(x1,y1)处为控制点,添加到当前点偏移 (x2,y2) 的贝塞尔曲线。

    注意: 该方法中的 (x1,y1) 和 (x2,y2) 都是以路径的当前点为原点计算的。

    该方法是调用 Path::relativeQuadraticBezierTo 方法, 这方法的实现如下:

    void CanvasPath::relativeQuadraticBezierTo(double x1,
                                               double y1,
                                               double x2,
                                               double y2) {
      mutable_path().rQuadTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                             SafeNarrow(y2));
      resetVolatility();
    }
    

    上述方法有调用了 SkPtahSkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) 方法,这个方法的实现如下:

    SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
        this->injectMoveToIfNeeded();  // This can change the result of this->getLastPt().
        SkPoint pt;
        this->getLastPt(&pt);
        return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
    }
    
  • 方法实例 在 flutter 中方法 relativeQuadraticBezierTo 的调用实例如下:

    void relativeQuadraticBezierTo(Canvas canvas) {
      final Path path = Path();
      path.moveTo(100, 200);
      path.quadraticBezierTo(200, 300, 300, 200);
      path.relativeQuadraticBezierTo(100, -100, 200, 0);
      canvas.drawPath(
        path,
        Paint()
          ..color = Colors.red
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2,
      );
    }
    

    伤处代码显示效果如下:

    Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~ (一)

    从上图显示可知, relativeQuadraticBezierTo 方法传入的 (x1,y2) 和 (x2,y2) 点的显示如上图浅蓝色标记所示。它是以 (300,100) 为 (0,0) 点计算的。

三、 未完待续

由于 Path 的方法很多和一篇文章篇幅不宜过长,所以进行对这个知识点进行多次文章分享。 文章中包含 Path 的实现底层代码和 SkPath 底层代码的介绍,如果理解有难度可以先学习一下 C++ 语言的知识。 如果你感觉文章还可以,请留下你的爱心和收藏以及评论