[》跳过拾光记忆]
拾光记忆
1-15.资产管理 Fam、手势触摸、枚举高阶用法、快速实现单选和多选、Diy 滑动轨道、水印功能、Image 高阶用法、矩阵16个参数含义、颜色差异、颜色填充、图像镜像、图像旋转、图像去色等功能的集合
简介: 该篇主要介绍15 篇文章
含有功能的目录,可根据自己的需求选择对应的功能介绍查看。
推荐: ⭐️⭐️⭐️⭐️⭐️
16. Flutter 之 IImage 图像反色处理
简介: 该篇主要介绍 Flutter 之 IImage 库中如何实现图像反色功能以及实现原理的介绍。
推荐: ⭐️⭐️⭐️⭐️⭐️
17. Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~(一)
18. Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~(二)
简介: 该篇主要介绍 Flutter
之 图形(Canvas
) 绘制路径 (Path
)基础功能以及方法实现底层代码的解析。
推荐: ⭐️⭐️⭐️⭐️⭐️
一、简述
路径(Path)
在图像绘制 (Canvas)
领域占有很大的地位。 你对各个开发语言的 Ptah 有多少的了解? 同时作为 Flutter 开发者,你又对 flutter 中的 Canvas-Path 又有多深的认知? 如果你感觉了解的还不是很多以及很深,这篇文章将带你进入 Path 深入了解之中。
二、 Path 方法详解
1. void addRect(Rect rect)
-
方法介绍
该方法是创建一个新的子路径,该路径是由四条线描述的矩形。该方法底层代码如下:
fluttervoid addRect(Rect rect) { assert(_rectIsValid(rect)); _addRect(rect.left, rect.top, rect.right, rect.bottom); }
上述方法,先断言传入的矩形是否可以描述,然后,再调用
void _addRect(double left, double top, double right, double bottom)
方法, 该方法又调用了Path::addRect
方法,这个方法的底层代码实现如下:c++void CanvasPath::addRect(double left, double top, double right, double bottom) { mutable_path().addRect(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right), SafeNarrow(bottom))); resetVolatility(); }
上述代码的第 2 行又调用
SkPath
的SkPath& SkPath::addRect(const SkRect &rect, SkPathDirection dir, unsigned startIndex)
方法,该方法的底层代码如下:c++SkPath& SkPath::addRect(const SkRect &rect, SkPathDirection dir, unsigned startIndex) { assert_known_direction(dir); this->setFirstDirection(this->hasOnlyMoveTos() ? (SkPathFirstDirection)dir : SkPathFirstDirection::kUnknown); SkAutoDisableDirectionCheck addc(this); SkAutoPathBoundsUpdate apbu(this, rect); SkDEBUGCODE(int initialVerbCount = this->countVerbs()); const int kVerbs = 5; // moveTo + 3x lineTo + close this->incReserve(kVerbs); SkPath_RectPointIterator iter(rect, dir, startIndex); this->moveTo(iter.current()); this->lineTo(iter.next()); this->lineTo(iter.next()); this->lineTo(iter.next()); this->close(); SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); return *this; }
从上述代码的第 15 -> 19 行可知是四条线段描述的矩形。
-
实例应用
在Flutter 中该方法的使用非常简单,代码如下:
fluttervoid addRect(Canvas canvas) { final Path path = Path(); path.moveTo(100, 200); path.lineTo(200, 300); path.addRect(const Rect.fromLTWH(100, 30, 100, 100)); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上面代码运行的视图效果如下:
2. void addOval(Rect oval)
-
方法介绍
该方法是新建一个路径,该路径是给定矩形的内切椭圆。该方法的底层调用代码如下:
fluttervoid addOval(Rect oval) { assert(_rectIsValid(oval)); _addOval(oval.left, oval.top, oval.right, oval.bottom); }
上述代码的第 3 行,又调用了
Path::addOval
方法, 该方法的底层代码如下:c++void CanvasPath::addOval(double left, double top, double right, double bottom) { mutable_path().addOval(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right), SafeNarrow(bottom))); resetVolatility(); }
上述代码的第 2 行,又调用了
SkPath
的SkPath& SkPath::addOval(const SkRect& oval, SkPathDirection dir)
方法, 该方法的底层实现如下:c++SkPath& SkPath::addOval(const SkRect& oval, SkPathDirection dir) { // legacy start index: 1 return this->addOval(oval, dir, 1); }
上述方法有调用
kPath& SkPath::addOval(const SkRect &oval, SkPathDirection dir, unsigned startPointIndex)
方法, 该方的底层实现如下:c++SkPath& SkPath::addOval(const SkRect &oval, SkPathDirection dir, unsigned startPointIndex) { assert_known_direction(dir); /* If addOval() is called after previous moveTo(), this path is still marked as an oval. This is used to fit into WebKit's calling sequences. We can't simply check isEmpty() in this case, as additional moveTo() would mark the path non empty. */ bool isOval = hasOnlyMoveTos(); if (isOval) { this->setFirstDirection((SkPathFirstDirection)dir); } else { this->setFirstDirection(SkPathFirstDirection::kUnknown); } SkAutoDisableDirectionCheck addc(this); SkAutoPathBoundsUpdate apbu(this, oval); SkDEBUGCODE(int initialVerbCount = this->countVerbs()); const int kVerbs = 6; // moveTo + 4x conicTo + close this->incReserve(kVerbs); SkPath_OvalPointIterator ovalIter(oval, dir, startPointIndex); // The corner iterator pts are tracking "behind" the oval/radii pts. SkPath_RectPointIterator rectIter(oval, dir, startPointIndex + (dir == SkPathDirection::kCW ? 0 : 1)); const SkScalar weight = SK_ScalarRoot2Over2; this->moveTo(ovalIter.current()); for (unsigned i = 0; i < 4; ++i) { this->conicTo(rectIter.next(), ovalIter.next(), weight); } this->close(); SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); SkPathRef::Editor ed(&fPathRef); ed.setIsOval(isOval, SkPathDirection::kCCW == dir, startPointIndex % 4); return *this; }
上述代码的第 29 -> 33 行是核心代码,该圆是有四条曲线组成。
-
实例应用
在 Flutter 中,该方的调用也是非常简单的,调用代码如下:
fluttervoid addOval(Canvas canvas) { final Path path = Path(); path.moveTo(100, 200); path.lineTo(200, 200); path.addOval(Rect.fromCenter(center: const Offset(150, 100), width: 100, height: 100)); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
3. void addArc(Rect oval, double startAngle, double sweepAngle)
-
方法介绍
该方法是创建新的路径,该路径是给定矩形内切椭圆的一段圆弧。该方法的底层代码如下:
fluttervoid addArc(Rect oval, double startAngle, double sweepAngle) { assert(_rectIsValid(oval)); _addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle); }
上述代码的第 3 行又调用了
Path::addArc
方法,该方法的底层代码是:c++void CanvasPath::addArc(double left, double top, double right, double bottom, double startAngle, double sweepAngle) { mutable_path().addArc( SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right), SafeNarrow(bottom)), SafeNarrow(startAngle) * 180.0f / static_cast<float>(M_PI), SafeNarrow(sweepAngle) * 180.0f / static_cast<float>(M_PI)); resetVolatility(); }
由上面代码可知
startAngle
和sweepAngle
传入的值是弧度
,而不是角度。上述代码的第 7 行又调用了SkPath
的SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle)
方法, 该方法的底层代码如下:c++SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) { if (oval.isEmpty() || 0 == sweepAngle) { return *this; } const SkScalar kFullCircleAngle = SkIntToScalar(360); if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) { // We can treat the arc as an oval if it begins at one of our legal starting positions. // See SkPath::addOval() docs. SkScalar startOver90 = startAngle / 90.f; SkScalar startOver90I = SkScalarRoundToScalar(startOver90); SkScalar error = startOver90 - startOver90I; if (SkScalarNearlyEqual(error, 0)) { // Index 1 is at startAngle == 0. SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f); startIndex = startIndex < 0 ? startIndex + 4.f : startIndex; return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW, (unsigned) startIndex); } } return this->arcTo(oval, startAngle, sweepAngle, true); }
- 上面代码第 2 - 4 行,介绍是如果
oval
为空和sweepAngle
为 0,则返回原始路径,不创建新路径。 - 上面代码第8 - 21 行,介绍是如果
sweepAngle
弧度大于 360 或者 小于 - 360 时,方法直接调用SkPath::addOval
方法生成给定矩形内切椭圆。 - 上面代码第 22 行,则是
sweepAngle
在 -360 和 360 之间,调用SkPath::arcTo
方法,在给定矩形内切椭圆的一段曲线。
- 上面代码第 2 - 4 行,介绍是如果
-
实例应用
在 Flutter 中该方的应用非常简单,该方法的实例调用如下:
fluttervoid addArc(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(300, 100); path.addArc(Rect.fromCenter(center: const Offset(150, 200), width: 200, height: 100), pi * 0.5, pi); path.addArc(Rect.fromCenter(center: const Offset(500, 200), width: 200, height: 100), pi * 0.5, pi * 3); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行视图如下:
4. void addPolygon(List<Offset> points, bool close)
-
方法介绍
该方法是创建新的路径,将给定的点依次连接起来形成的路径。而
close
参数是决定新路径的起点和终点是否相连。该方法底层调用了Path::addPolygon
方法,这个方法的底层代码如下:c++void CanvasPath::addPolygon(const tonic::Float32List& points, bool close) { mutable_path().addPoly(reinterpret_cast<const SkPoint*>(points.data()), points.num_elements() / 2, close); resetVolatility(); }
上述代码的第 2 行又调用了
SkPath
的SkPath::Polygon(const SkPoint pts[], int count, bool isClosed, SkPathFillType ft, bool isVolatile)
方法,该方法的底层代码如下:c++SkPath SkPath::Polygon(const SkPoint pts[], int count, bool isClosed, SkPathFillType ft, bool isVolatile) { return SkPathBuilder().addPolygon(pts, count, isClosed) .setFillType(ft) .setIsVolatile(isVolatile) .detach(); }
上述代码第 3 行,调用了
SkPathBuilder
的addPolygonn
方法,该方法的底层实现如下:c++SkPathBuilder& SkPathBuilder::addPolygon(const SkPoint pts[], int count, bool isClosed) { if (count <= 0) { return *this; } this->moveTo(pts[0]); this->polylineTo(&pts[1], count - 1); if (isClosed) { this->close(); } return *this; }
上述代码的第 6-7 行代码是实现的核心代码。
-
实例应用
在 Flutter 中,该方法的调用很简单如下所示:
fluttervoid addPolygon(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 100); path.addPolygon(const [Offset(300, 100), Offset(400, 200), Offset(300, 300)], false); path.addPolygon(const [Offset(500, 100), Offset(600, 100), Offset(500, 200)], true); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
注意: 上面形成的线,是我们传递poins
中点的顺序连线的;这些点的参考坐标原点是 (0,0) 点。
5. void addRRect(RRect rrect)
-
方法介绍
该方法是新创建一个子路径,该路径是由四条直线和四个圆弧组成的圆角矩形。该方法的底层代码调用了
Path::addRRect
方法,这个方法的底层实现如下:c++void CanvasPath::addRRect(const RRect& rrect) { mutable_path().addRRect(rrect.sk_rrect); resetVolatility(); }
上述代码的第 2 行又调用了
SkPath
的SkPath& SkPath::addRRect(const SkRRect &rrect, SkPathDirection dir, unsigned startIndex)
方法,该方法的底层实现如下:c++SkPath& SkPath::addRRect(const SkRRect &rrect, SkPathDirection dir, unsigned startIndex) { assert_known_direction(dir); bool isRRect = hasOnlyMoveTos(); const SkRect& bounds = rrect.getBounds(); if (rrect.isRect() || rrect.isEmpty()) { // degenerate(rect) => radii points are collapsing this->addRect(bounds, dir, (startIndex + 1) / 2); } else if (rrect.isOval()) { // degenerate(oval) => line points are collapsing this->addOval(bounds, dir, startIndex / 2); } else { this->setFirstDirection(this->hasOnlyMoveTos() ? (SkPathFirstDirection)dir : SkPathFirstDirection::kUnknown); SkAutoPathBoundsUpdate apbu(this, bounds); SkAutoDisableDirectionCheck addc(this); // we start with a conic on odd indices when moving CW vs. even indices when moving CCW const bool startsWithConic = ((startIndex & 1) == (dir == SkPathDirection::kCW)); const SkScalar weight = SK_ScalarRoot2Over2; SkDEBUGCODE(int initialVerbCount = this->countVerbs()); const int kVerbs = startsWithConic ? 9 // moveTo + 4x conicTo + 3x lineTo + close : 10; // moveTo + 4x lineTo + 4x conicTo + close this->incReserve(kVerbs); SkPath_RRectPointIterator rrectIter(rrect, dir, startIndex); // Corner iterator indices follow the collapsed radii model, // adjusted such that the start pt is "behind" the radii start pt. const unsigned rectStartIndex = startIndex / 2 + (dir == SkPathDirection::kCW ? 0 : 1); SkPath_RectPointIterator rectIter(bounds, dir, rectStartIndex); this->moveTo(rrectIter.current()); if (startsWithConic) { for (unsigned i = 0; i < 3; ++i) { this->conicTo(rectIter.next(), rrectIter.next(), weight); this->lineTo(rrectIter.next()); } this->conicTo(rectIter.next(), rrectIter.next(), weight); // final lineTo handled by close(). } else { for (unsigned i = 0; i < 4; ++i) { this->lineTo(rrectIter.next()); this->conicTo(rectIter.next(), rrectIter.next(), weight); } } this->close(); SkPathRef::Editor ed(&fPathRef); ed.setIsRRect(isRRect, dir == SkPathDirection::kCCW, startIndex % 8); SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); } SkDEBUGCODE(fPathRef->validate();) return *this; }
注意:该方法传入参数
RRect
的参考坐标系。 -
实例应用
该方法在 Flutter 中实例如下:
fluttervoid addRRect(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); path.addRRect(RRect.fromLTRBR(300, 200, 500, 100, const Radius.circular(10))); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
需要注意,
RRect
顶部和底部位置以及坐标。
6. void addPath(Path path, Offset offset, {Float64List? matrix4})
-
方法介绍
添加新的路径,可以设置新路径的偏移以及矩阵处理。该方法底层代码如下:
fluttervoid addPath(Path path, Offset offset, {Float64List? matrix4}) { assert(path != null); // path is checked on the engine side assert(_offsetIsValid(offset)); if (matrix4 != null) { assert(_matrix4IsValid(matrix4)); _addPathWithMatrix(path, offset.dx, offset.dy, matrix4); } else { _addPath(path, offset.dx, offset.dy); } }
从上面代码可知,
-
如果
matrix4 == null
, 则方法执行第 8 行代码,调用Path::addPath
方法,该方法的底层实现如下:c++void CanvasPath::addPath(CanvasPath* path, double dx, double dy) { if (!path) { Dart_ThrowException(ToDart("Path.addPath called with non-genuine Path.")); return; } mutable_path().addPath(path->path(), SafeNarrow(dx), SafeNarrow(dy), SkPath::kAppend_AddPathMode); resetVolatility(); }
上述方法第 6 行又调用了
SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode)
该方法的实现如下:c++SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) { SkMatrix matrix; matrix.setTranslate(dx, dy); return this->addPath(path, matrix, mode); }
从上面代码的第 4 行可知,我们传入的
Offset
最后还是经过矩阵偏移来实现的。 -
如果
matrix4 != null
, 这代码执行第 6 行代码调用Path::addPathWithMatrix
方法,该方法的底层实现如下:c++void CanvasPath::addPathWithMatrix(CanvasPath* path, double dx, double dy, Dart_Handle matrix4_handle) { tonic::Float64List matrix4(matrix4_handle); if (!path) { matrix4.Release(); Dart_ThrowException( ToDart("Path.addPathWithMatrix called with non-genuine Path.")); return; } SkMatrix matrix = ToSkMatrix(matrix4); matrix4.Release(); matrix.setTranslateX(matrix.getTranslateX() + SafeNarrow(dx)); matrix.setTranslateY(matrix.getTranslateY() + SafeNarrow(dy)); mutable_path().addPath(path->path(), matrix, SkPath::kAppend_AddPathMode); resetVolatility(); }
上述方法第 18 行又调用了
SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode)
方法, 该方法的底层实现如下:c++SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) { if (srcPath.isEmpty()) { return *this; } // Detect if we're trying to add ourself const SkPath* src = &srcPath; SkTLazy<SkPath> tmp; if (this == src) { src = tmp.set(srcPath); } if (kAppend_AddPathMode == mode && !matrix.hasPerspective()) { if (src->fLastMoveToIndex >= 0) { fLastMoveToIndex = src->fLastMoveToIndex + this->countPoints(); } else { fLastMoveToIndex = src->fLastMoveToIndex - this->countPoints(); } SkPathRef::Editor ed(&fPathRef); auto [newPts, newWeights] = ed.growForVerbsInPath(*src->fPathRef); matrix.mapPoints(newPts, src->fPathRef->points(), src->countPoints()); if (int numWeights = src->fPathRef->countWeights()) { memcpy(newWeights, src->fPathRef->conicWeights(), numWeights * sizeof(newWeights[0])); } return this->dirtyAfterEdit(); } SkMatrixPriv::MapPtsProc mapPtsProc = SkMatrixPriv::GetMapPtsProc(matrix); bool firstVerb = true; for (auto [verb, pts, w] : SkPathPriv::Iterate(*src)) { SkPoint mappedPts[3]; switch (verb) { case SkPathVerb::kMove: mapPtsProc(matrix, mappedPts, &pts[0], 1); if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) { injectMoveToIfNeeded(); // In case last contour is closed SkPoint lastPt; // don't add lineTo if it is degenerate if (!this->getLastPt(&lastPt) || lastPt != mappedPts[0]) { this->lineTo(mappedPts[0]); } } else { this->moveTo(mappedPts[0]); } break; case SkPathVerb::kLine: mapPtsProc(matrix, mappedPts, &pts[1], 1); this->lineTo(mappedPts[0]); break; case SkPathVerb::kQuad: mapPtsProc(matrix, mappedPts, &pts[1], 2); this->quadTo(mappedPts[0], mappedPts[1]); break; case SkPathVerb::kConic: mapPtsProc(matrix, mappedPts, &pts[1], 2); this->conicTo(mappedPts[0], mappedPts[1], *w); break; case SkPathVerb::kCubic: mapPtsProc(matrix, mappedPts, &pts[1], 3); this->cubicTo(mappedPts[0], mappedPts[1], mappedPts[2]); break; case SkPathVerb::kClose: this->close(); break; } firstVerb = false; } return *this; }
上述代码就是核心实现方法,主要是处理不同
Path
(线、弧、圆、曲线等) 经过矩阵得到新的路径。
-
-
实例应用
该方法在Flutter 中的应用还是非常简单的,调用实例如下:
-
方法参数不传
matrix4
的实例如下:fluttervoid addPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); path.addPath(path1, const Offset(100, 0)); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上面代码运行视图效果如下:
-
方法传递
matrix4
参数的实例如下:-
移动
fluttervoid addPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); matrix4.translate(100.0, 100.0); path.addPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
-
缩放
fluttervoid addPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); // matrix4.translate(100.0, 100.0); matrix4.scale(2.0); path.addPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
-
旋转
fluttervoid addPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); // matrix4.translate(100.0, 100.0); // matrix4.scale(2.0); matrix4.rotateZ(pi / 3); path.addPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
总结: 如果你对
Matrix4
的操作还是不是很了解,请查看 Flutter 中的 Matrix4,它有 16 个参数,您都知道它们的含义吗? 这篇文章。 -
-
7. void extendWithPath(Path path, Offset offset, {Float64List? matrix4})
- 方法介绍
该方法和void addPath(Path path, Offset offset, {Float64List? matrix4})
方法的参数以及参数的意义都是一样的。唯一不同的是void addPath(Path path, Offset offset, {Float64List? matrix4})
的视图效果是路径分离的,而该方法的视图效果是将原有路径和新加路径进行连线。 - 实例应用\
-
不传入
matrix4
参数fluttervoid extendWithPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); // // // matrix4.translate(100.0, 100.0); // // // matrix4.scale(2.0); // matrix4.rotateZ(pi / 3); path.extendWithPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码的运行实例如下:
-
传入
matrix4
参数-
平移
fluttervoid extendWithPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); matrix4.translate(100.0, 100.0); // // // matrix4.scale(2.0); // matrix4.rotateZ(pi / 3); path.extendWithPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行实例如下:
-
缩放
fluttervoid extendWithPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); // matrix4.translate(100.0, 100.0); matrix4.scale(2.0); // matrix4.rotateZ(pi / 3); path.extendWithPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行实例如下:
-
旋转
fluttervoid extendWithPath(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); final Matrix4 matrix4 = Matrix4.identity(); // matrix4.translate(100.0, 100.0); // matrix4.scale(2.0); matrix4.rotateZ(pi / 3); path.extendWithPath(path1, const Offset(0, 0), matrix4: matrix4.storage); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行视图效果如下:
总结: 如果你对
Matrix4
的操作还是不是很了解,请查看 Flutter 中的 Matrix4,它有 16 个参数,您都知道它们的含义吗? 这篇文章。 -
-
8. void close()
-
方法介绍
该方法是关闭最后一个子路径。该方法底层实现如下:
c++void CanvasPath::close() { mutable_path().close(); resetVolatility(); }
上述代码第 2 行又调用了
SkPath& SkPath::close()
方法, 该方法底层实现如下:c++SkPath& SkPath::close() { SkDEBUGCODE(this->validate();) int count = fPathRef->countVerbs(); if (count > 0) { switch (fPathRef->atVerb(count - 1)) { case kLine_Verb: case kQuad_Verb: case kConic_Verb: case kCubic_Verb: case kMove_Verb: { SkPathRef::Editor ed(&fPathRef); ed.growForVerb(kClose_Verb); break; } case kClose_Verb: // don't add a close if it's the first verb or a repeat break; default: SkDEBUGFAIL("unexpected verb"); break; } } // signal that we need a moveTo to follow us (unless we're done) #if 0 if (fLastMoveToIndex >= 0) { fLastMoveToIndex = ~fLastMoveToIndex; } #else fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1); #endif return *this; }
-
实例应用
该方法在 Flutter 中调用非常简单,但是也需要注意的是路径单独路还是有子路径。
-
有子路径时,
close
关闭的是最后一个子路径fluttervoid close(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); path1.lineTo(400, 400); // path1.close(); path.addPath(path1, Offset.zero); path.close(); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下:
-
只有一个路径,
close
是关闭整个路径fluttervoid close(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); path1.lineTo(400, 500); // path1.close(); path.extendWithPath(path1, Offset.zero); path.close(); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行实例如下:
-
9. void reset()
-
方法介绍
删除所有子路径,将路径重置初始状态(
Path path = Path();
)。该方法底层实现如下所示:c++void CanvasPath::reset() { mutable_path().reset(); resetVolatility(); }
上述代码第 2 行,又调用了
SkPath
的SkPath& SkPath::reset()
方法, 该方的底层实现如下:c++SkPath& SkPath::reset() { SkDEBUGCODE(this->validate();) if (fPathRef->unique()) { fPathRef->reset(); } else { fPathRef.reset(SkPathRef::CreateEmpty()); } this->resetFields(); return *this; }
-
实例应用
该方法在 Flutter 中使用很简单,如下说是:
fluttervoid reset(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 100); path1.lineTo(400, 200); path.addPath(path1, Offset.zero); path.reset(); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
无论原始路径有多少个子路径都会被清除,路径被重置。
10. bool contains(Offset point)
-
方法介绍
该方法是测试给定的点是否在路径内。该方的底层代码如下:
flutterbool contains(Offset point) { assert(_offsetIsValid(point)); return _contains(point.dx, point.dy); } @FfiNative<Bool Function(Pointer<Void>, Double, Double)>('Path::contains', isLeaf: true) external bool _contains(double x, double y);
上述代码第7行又调用了
Path::contains
方法, 该方的底层实现如下:c++bool CanvasPath::contains(double x, double y) { return path().contains(SafeNarrow(x), SafeNarrow(y)); }
上边代码的第2行调用
SkPath::contains
方法,该方法的实现如下:c++bool SkPath::contains(SkScalar x, SkScalar y) const { bool isInverse = this->isInverseFillType(); if (this->isEmpty()) { return isInverse; } if (!contains_inclusive(this->getBounds(), x, y)) { return isInverse; } SkPath::Iter iter(*this, true); bool done = false; int w = 0; int onCurveCount = 0; do { SkPoint pts[4]; switch (iter.next(pts)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: w += winding_line(pts, x, y, &onCurveCount); break; case SkPath::kQuad_Verb: w += winding_quad(pts, x, y, &onCurveCount); break; case SkPath::kConic_Verb: w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount); break; case SkPath::kCubic_Verb: w += winding_cubic(pts, x, y, &onCurveCount); break; case SkPath::kDone_Verb: done = true; break; } } while (!done); bool evenOddFill = SkPathFillType::kEvenOdd == this->getFillType() || SkPathFillType::kInverseEvenOdd == this->getFillType(); if (evenOddFill) { w &= 1; } if (w) { return !isInverse; } if (onCurveCount <= 1) { return SkToBool(onCurveCount) ^ isInverse; } if ((onCurveCount & 1) || evenOddFill) { return SkToBool(onCurveCount & 1) ^ isInverse; } // If the point touches an even number of curves, and the fill is winding, check for // coincidence. Count coincidence as places where the on curve points have identical tangents. iter.setPath(*this, true); done = false; SkTDArray<SkVector> tangents; do { SkPoint pts[4]; int oldCount = tangents.size(); switch (iter.next(pts)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: tangent_line(pts, x, y, &tangents); break; case SkPath::kQuad_Verb: tangent_quad(pts, x, y, &tangents); break; case SkPath::kConic_Verb: tangent_conic(pts, x, y, iter.conicWeight(), &tangents); break; case SkPath::kCubic_Verb: tangent_cubic(pts, x, y, &tangents); break; case SkPath::kDone_Verb: done = true; break; } if (tangents.size() > oldCount) { int last = tangents.size() - 1; const SkVector& tangent = tangents[last]; if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) { tangents.remove(last); } else { for (int index = 0; index < last; ++index) { const SkVector& test = tangents[index]; if (SkScalarNearlyZero(test.cross(tangent)) && SkScalarSignAsInt(tangent.fX * test.fX) <= 0 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) { tangents.remove(last); tangents.removeShuffle(index); break; } } } } } while (!done); return SkToBool(tangents.size()) ^ isInverse; }
-
实例应用
该方法的使用也是非常的简单的,调用代码如下:
fluttervoid contains(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 100); path.lineTo(200, 200); // path.close(); final bool isContains = path.contains(const Offset(150, 150)); canvas.drawPath( path, Paint() ..color = isContains ? Colors.red : Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
下面是视图是路径是否
close
的视图效果,如下:从上图可知,测试的点只要是在路径闭合的区域内,无论你是否闭合路径,测试的结果都是
true
。需要注意的是在路径有很多子路径时,检测点是检测点是否在最后一个路径的闭合区域。 视图展示如下:
如果检测点位于绿色区域,检测的结果是
false
; 如果检测点位于蓝色区域,检测的结果是true
。注意,如果检测点事上图左边红色斜线的起点和终点,检测的结果是true
。
11. Path shift(Offset offset)
-
方法介绍
该方法是将路径所有子路径全部复制一份,然后添加给定的偏移量。该方法的底层调用代码是:
flutterPath shift(Offset offset) { assert(_offsetIsValid(offset)); final Path path = Path._(); _shift(path, offset.dx, offset.dy); return path; }
上述代码第 3 行是生成新的路径;第 4 行调用
Path::shift
方法,该方法的底层实现如下:c++void CanvasPath::shift(Dart_Handle path_handle, double dx, double dy) { fml::RefPtr<CanvasPath> path = Create(path_handle); auto& other_mutable_path = path->mutable_path(); mutable_path().offset(SafeNarrow(dx), SafeNarrow(dy), &other_mutable_path); resetVolatility(); }
-
实例应用
该方法在 Flutter 中的调用实例非常简单,如下所示:
fluttervoid shift(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(200, 100); path1.lineTo(300, 200); path.addPath(path1, Offset.zero); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); final Path path2 = path.shift(const Offset(200, 200)); canvas.drawPath( path2, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行效果如下:
12. Path transform(Float64List matrix4)
-
方法介绍
创建路径的所有子路径,经过矩阵转换后的副本。该方法底层调用如下:
flutterPath transform(Float64List matrix4) { assert(_matrix4IsValid(matrix4)); final Path path = Path._(); _transform(path, matrix4); return path; }
上述方法的第 3 行,又调用了
Path::transform
方法,该方法的底层代码实现如下:c++void CanvasPath::transform(Dart_Handle path_handle, Dart_Handle matrix4_handle) { tonic::Float64List matrix4(matrix4_handle); auto sk_matrix = ToSkMatrix(matrix4); matrix4.Release(); fml::RefPtr<CanvasPath> path = Create(path_handle); auto& other_mutable_path = path->mutable_path(); mutable_path().transform(sk_matrix, &other_mutable_path); }
-
实例应用
该方法在Flutter 中的代码调用如下:
-
矩阵平移
fluttervoid transform(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); final Matrix4 matrix4 = Matrix4.identity(); matrix4.translate(200.0); final Path path1 = path.transform(matrix4.storage); canvas.drawPath( path1, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的实例如下:
-
矩阵缩放
避免路径重合,就增加偏移量。
fluttervoid transform(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); final Matrix4 matrix4 = Matrix4.identity(); matrix4.translate(200.0); matrix4.scale(2.0); final Path path1 = path.transform(matrix4.storage); canvas.drawPath( path1, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码的运行视图如下:
-
矩阵旋转
fluttervoid transform(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); final Matrix4 matrix4 = Matrix4.identity(); // matrix4.translate(200.0); // matrix4.scale(2.0); matrix4.rotateZ(pi / 6); final Path path1 = path.transform(matrix4.storage); canvas.drawPath( path1, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行实例如下:
总结: 如果你对
Matrix4
的操作还是不是很了解,请查看 Flutter 中的 Matrix4,它有 16 个参数,您都知道它们的含义吗? 这篇文章。 -
13. Rect getBounds()
-
方法介绍
该方法是计算路径的边界矩形。该方法的底层代码如下:
flutterRect getBounds() { final Float32List rect = _getBounds(); return Rect.fromLTRB(rect[0], rect[1], rect[2], rect[3]); }
上述代码的第 2 行是关键,则
_getBounds()
方法的实现如下:c++const SkRect& SkPath::getBounds() const { return fPathRef->getBounds(); }
上述代码第 2 行又调用了
SkPathRef.h
的const SkRect& getBounds()
方法,该方法的实现如下:c++const SkRect& getBounds() const { if (fBoundsIsDirty) { this->computeBounds(); } return fBounds; }
上述方法的第 3 行
computeBounds()
方法的实现如下:c++void computeBounds() const { SkDEBUGCODE(this->validate();) // TODO: remove fBoundsIsDirty and fIsFinite, // using an inverted rect instead of fBoundsIsDirty and always recalculating fIsFinite. SkASSERT(fBoundsIsDirty); fIsFinite = ComputePtBounds(&fBounds, *this); fBoundsIsDirty = false; }
-
实例应用
该方法在Flutter 中的应用十分简单,调用代码如下:
fluttervoid getBounds(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(200, 100); path1.lineTo(300, 300); // path.addPath(path1, Offset.zero); path.extendWithPath(path1, Offset.zero); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); canvas.drawRect( path.getBounds(), Paint() ..color = Colors.red ..style = PaintingStyle.stroke ..strokeWidth = 2, ); }
上述代码运行的视图效果如下(包含注释代码):
14. static Path combine(PathOperation operation, Path path1, Path path2)
-
方法介绍
根据指定形式组合两条路径生成新的路径。该方法的底层代码如下:
flutterstatic Path combine(PathOperation operation, Path path1, Path path2) { assert(path1 != null); assert(path2 != null); final Path path = Path(); if (path._op(path1, path2, operation.index)) { return path; } throw StateError('Path.combine() failed. This may be due an invalid path; in particular, check for NaN values.'); }
上述代码的第5行调用了
Path::op
方法,该方法的底层实现如下:c++bool CanvasPath::op(CanvasPath* path1, CanvasPath* path2, int operation) { return Op(path1->path(), path2->path(), static_cast<SkPathOp>(operation), &tracked_path_->path); resetVolatility(); }
-
实例应用
该方法在Flutter 中的调用也非常简单,下面我们根据路径组合形式展示。路径的组合形式由
PathOperation
控制。下面我们对PathOperation
介绍一下: PathOperation 是路径整合形式控制参数,它是个枚举;它的枚举类型如下:difference
这是从第一个路径中减去第二个路径并生成新的路径。视图说明:
intersect
这是将两个路径重叠的部分创建为一个新的路径。视图说明:
union
这是将两个路径进行合并创建一个新的路径。视图说明如下:
xor
这是将两个路径进行异或创建新的路径。视图说明:
上面绿色部分是新的路径,黑色是去除的部分。
reverseDifference
这是将从第二个路径中减去第一个路径生成新的路径。视图效果如下:
上述是路径组合
PathOperation
枚举类型的介绍。在 Flutter 中该方法的调用如下:
fluttervoid combine(Canvas canvas) { final Path path1 = Path(); path1.moveTo(200, 100); path1.lineTo(100, 300); path1.lineTo(300, 300); path1.close(); // canvas.drawPath( // path1, // Paint() // ..color = Colors.red // ..style = PaintingStyle.stroke // ..strokeWidth = 2, // ); final Path path2 = Path(); path2.moveTo(250, 200); path2.lineTo(400, 200); path2.lineTo(300, 400); path2.lineTo(150, 400); path2.close(); // canvas.drawPath( // path2, // Paint() // ..color = Colors.green // ..style = PaintingStyle.stroke // ..strokeWidth = 2, // ); final Path path = Path.combine(PathOperation.reverseDifference, path1, path2); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.fill ..strokeWidth = 2, ); }
上面代码是路径联合的测试代码;下面是路径联合的视图效果, 如下所示:
-
原始两个路径
-
不同组合形式的视图
15. PathMetrics computeMetrics({bool forceClosed = false})
-
方法介绍
该方法是对路径进行测量,可以获取路径相关的信息。该方法的底层实现如下:
flutterPathMetrics computeMetrics({bool forceClosed = false}) { return PathMetrics._(this, forceClosed); }
上述代码第2行,又调用了
PathMetrics
的私有方法进行初始化,该方法的底层实现如下:flutterclass PathMetrics extends collection.IterableBase<PathMetric> { PathMetrics._(Path path, bool forceClosed) : _iterator = PathMetricIterator._(_PathMeasure(path, forceClosed)); final Iterator<PathMetric> _iterator; @override Iterator<PathMetric> get iterator => _iterator; }
上述代码的第2行,
_PathMeasure
初始化是核心方法,该方法的底层实现如下:flutterclass _PathMeasure extends NativeFieldWrapperClass1 { _PathMeasure(Path path, bool forceClosed) { _constructor(path, forceClosed); } @FfiNative<Void Function(Handle, Pointer<Void>, Bool)>('PathMeasure::Create') external void _constructor(Path path, bool forceClosed); double length(int contourIndex) { assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); return _length(contourIndex); } @FfiNative<Float Function(Pointer<Void>, Int32)>('PathMeasure::getLength', isLeaf: true) external double _length(int contourIndex); Tangent? getTangentForOffset(int contourIndex, double distance) { assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); final Float32List posTan = _getPosTan(contourIndex, distance); // first entry == 0 indicates that Skia returned false if (posTan[0] == 0.0) { return null; } else { return Tangent(Offset(posTan[1], posTan[2]), Offset(posTan[3], posTan[4])); } } @FfiNative<Handle Function(Pointer<Void>, Int32, Float)>('PathMeasure::getPosTan') external Float32List _getPosTan(int contourIndex, double distance); Path extractPath(int contourIndex, double start, double end, {bool startWithMoveTo = true}) { assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); final Path path = Path._(); _extractPath(path, contourIndex, start, end, startWithMoveTo); return path; } @FfiNative<Void Function(Pointer<Void>, Handle, Int32, Float, Float, Bool)>('PathMeasure::getSegment') external void _extractPath(Path outPath, int contourIndex, double start, double end, bool startWithMoveTo); bool isClosed(int contourIndex) { assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); return _isClosed(contourIndex); } @FfiNative<Bool Function(Pointer<Void>, Int32)>('PathMeasure::isClosed', isLeaf: true) external bool _isClosed(int contourIndex); // Move to the next contour in the path. // // A path can have a next contour if [Path.moveTo] was called after drawing began. // Return true if one exists, or false. bool _nextContour() { final bool next = _nativeNextContour(); if (next) { currentContourIndex++; } return next; } @FfiNative<Bool Function(Pointer<Void>)>('PathMeasure::nextContour', isLeaf: true) external bool _nativeNextContour(); /// The index of the current contour in the list of contours in the path. /// /// [nextContour] will increment this to the zero based index. int currentContourIndex = -1; }
-
实例应用
该方法在Flutter 中的使用也很简单,如下所示:
fluttervoid computeMetrics(Canvas canvas) { final Path path = Path(); path.moveTo(100, 100); path.lineTo(200, 200); final Path path1 = Path(); path1.moveTo(300, 300); path1.lineTo(400, 400); path.extendWithPath(path1, Offset.zero); canvas.drawPath( path, Paint() ..color = Colors.purple ..style = PaintingStyle.stroke ..strokeWidth = 2, ); final PathMetrics pathMetrics = path.computeMetrics(); final PathMetric pathMetric = pathMetrics.first; // 路径索引 print(pathMetric.contourIndex); // 路径是否关闭 print(pathMetric.isClosed); // 路径的长度 print(pathMetric.length); // 扩展新的路径 print(pathMetric.extractPath(10, 20)); // 计算给定路径偏移的位置以及切角 print(pathMetric.getTangentForOffset(10)); }
总结: 对于路径测量还有很多细节问题,这些在日常的开发中也使用很少。 对于这个方法在做一些图像绘制的软件可能要求了解更多,对于我们一般的开发中,知道这些你就很不简单了。
三、鼓励与支持
Path 相关的方法到此完全介绍完毕。总体来看,Path 的方法非常丰富,我们分成了三篇文章来介绍它。如果你对Flutter 中的路径绘制还不太了解,我建议你来这里看看。尽管内容很多,我们还是一点一点地介绍完了。如果你觉得我的介绍还可以,那么请给我点个关注、收藏或者评论,以及转发。这些都是对我认可和鼓励的表达。本篇文章涉及到的示例代码地址如下:Flutter 的路径绘制 Path 的详细介绍实例代码地址.