一、ClipPath 的使用
1. 认识 ClipPath
ClipPath
继承自 SingleChildRenderObjectWidget
,说明该组件可以传入一个组件入参。
ClipPath
的构造方法中可以,传入 clipper
和 clipBehavior
两个参数,分别代表裁剪路径
和 裁剪行为
。
final CustomClipper<Path>? clipper;
final Clip clipBehavior;
2. ClipPath 的简单使用
clipper
类型为 CustomClipper<Path>
,可以看出它是一个 抽象类
,所以无法直接实例化对象,所以需要找到可用实现类,或自己实现。在 Flutter 框架中
只有 ShapeBorderClipper
可用。
ShapeBorderClipper
需要传入一个 ShapeBorder
对象。
ShapeBorder
也是个抽象类,Flutter
中内置了很多的 ShapeBorder
子类。
如下,是通过 CircleBorder
和 RoundedRectangleBorder
两个形状进行裁剪的案例。
// 圆形裁剪
ClipPath(
clipper: ShapeBorderClipper(
shape: CircleBorder(),
),
child: Image.asset(
'assets/images/icon_head.jpg',
width: 100,
height: 100,
),
)
// 圆角矩形裁剪
ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)
),
),
child: Image.asset(
'assets/images/icon_head.jpg',
width: 100,
height: 100,
),
),
3.ClipPath 的 shape
方法
既然框架中 CustomClipper
只有 ShapeBorderClipper
子类,那么就可以简化使用。如下,通过 shape
方法返回 Widget
组件,只需要传入 shape
即可。从源码中可以看出,其实就是简单封装一下 ShapeBorderClipper
而已。
// 使用 ClipPath.shape 简化代码
ClipPath.shape(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
child: Image.asset(
'assets/images/icon_head.jpg',
width: 100,
height: 100,
),
),
4. clipBehavior 属性
clipBehavior
属性对应的类型为 Clip
枚举,有如下四个元素。它用来表示组件内容裁剪
的方式。在这里中默认是 antiAlias
,这种方式是抗锯齿的裁剪,也就是说裁剪成曲线时不会产生锯齿感。
/// Different ways to clip a widget's content.
enum Clip {
none, // 无
hardEdge, // 硬边缘
antiAlias, // 抗锯齿
antiAliasWithSaveLayer, // 抗锯齿 存储层
}
至于其他几个,none
是不进行裁剪,一般我们默认组件不会超过边界,但如果内容会溢出边界,我们需要指定后三种裁剪方式之一。hardEdge
是不抗锯齿的意思,这种裁剪方式当是曲线路径裁剪时,会有明显的锯齿状,好处是这种方式要比 antiAlias
快一些,适合用于矩形裁剪。另外 antiAliasWithSaveLayer
模式不仅抗锯齿,而且还会分配一个缓冲区。后续所有的绘制都在缓冲区上进行,最后被剪切和合成。这种方式要更慢,一般很少使用。
5. 使用 ClipPath 的注意点
源码中说,通过路径裁剪是比较昂贵的,对于一些常规的裁剪,可以考虑其他组件,比如矩形裁剪可以使用 ClipRect
,圆或椭圆可以使用 ClipOval
,圆角矩形可以使用 ClipRRect
。
其实这么一看 ClipPath
并非用于通常裁剪,对于一些特殊的裁剪需求,如果是按照某些曲线进行裁剪,那 ClipPath
就是可以胜任。
二、自定义裁剪
上面也说过 CustomClipper
在框架中只有一个子类,使用如果我们想要组定义裁剪性质,就需要自定义裁剪器。那首先我们先认识一下 CustomClipper
。
1. 认识 CustomClipper 裁剪器
CustomClipper
继承自Listenable
可指定泛型,有两个抽象方法 getClip
和 shouldReclip
。其实看到这里可以联系到 CustomPainter
,这两个抽象在结构上非常类似。都可以通过一个可监听对象触发重新裁剪/重绘
,都可以通过shouldXXX
判断读取类对象更新时是否重新裁剪/重绘
。
下面先定义一个三角形的路径裁剪测试一下,主要就是在 getClip
中返回对应裁剪的路径。
class TriangleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
print(size);
Path path = Path()
..moveTo(0, size.height)
..relativeLineTo(size.width, 0)
..relativeLineTo(-size.width / 2, -size.height)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<dynamic> oldClipper) {
return true;
}
}
2. 自定义爱心裁剪
只要是路径,都可以进行裁剪。如下是一个简单的爱心路径裁剪,这里使用的贝塞尔曲线,正好也来看一下 antiAlias
和 hardEdge
的表现效果,你放大一下可以看出使用 hardEdge
类型的裁剪效果周围有明显锯齿。
class LoveClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double fate = 18.5*size.height/100;
double width = size.width / 2;
double height = size.height / 4;
Path path = Path();
path.moveTo(width, height);
path.cubicTo(width, height, width 1.1 * fate, height - 1.5 * fate, width 2 * fate, height);
path.cubicTo(width 2 * fate, height, width 3.5 * fate, height 2 * fate, width, height 4 * fate);
path.moveTo(width, height);
path.cubicTo(width, height, width - 1.1 * fate, height - 1.5 * fate, width - 2 * fate, height);
path.cubicTo(width - 2 * fate, height, width - 3.5 * fate, height 2 * fate, width, height 4 * fate);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
3.打洞裁剪
在 【Flutter高级玩法-shape】Path在手,天下我有 一文中介绍过基于 path
自定义 ShapeBorder
的使用,其实这里也是类似的。你可以操作路径进行任意地裁剪,当然那篇文章是自定义 ShapeBorder
,也可以通过 ShapeBorderClipper
应用到 ClipPath
中。
class HoleClipper extends CustomClipper<Path> {
final Offset offset;
final double holeSize;
HoleClipper({this.offset=const Offset(0.1, 0.1), this.holeSize=20});
@override
Path getClip(Size size) {
Path circlePath = Path();
circlePath.addRRect(RRect.fromRectAndRadius(Offset.zero&size, Radius.circular(5)));
double w = size.width;
double h = size.height;
Offset offsetXY = Offset( offset.dx*w,offset.dy*h);
double d = holeSize;
_getHold(circlePath, 1, d, offsetXY);
circlePath.fillType = PathFillType.evenOdd;
return circlePath;
}
void _getHold(Path path, int count, double d, Offset offset) {
var left = offset.dx;
var top = offset.dy;
var right = left d;
var bottom = top d;
path.addOval(Rect.fromLTRB(left, top, right, bottom));
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
如果要在 ClipPath
使用自定义路径裁剪,推荐直接继承自 CustomClipper
来创建子类。而非自定义 ShapeBorder
,再通过 ShapeBorderClipper
在 ClipPath
中使用,因为自定义 ShapeBorder
比较复杂,还能进行绘制,但是绘制的东西在 ClipPath
时不会被画出来,此处只是根据路径裁剪。通过 CustomClipper
比较方便,而且可以控制是否需要重新裁剪,以及通过 Listenable
对象触发重新裁剪,这样就可以进行裁剪动画。
三、ClipPath 的源码实现简看
实现,它继承自 SingleChildRenderObjectWidget
。
就说明,该组件需要维护一个 RenderObject
对象的创建及更新,如下是 RenderClipPath
。
在 RenderClipPath#paint
时,会触发 context#pushClipPath
方法,创建一个 layer
。
在 pushClipPath
中如果需要合成 needsCompositing
,则会创建 ClipPathLayer
执行裁剪工作。
否则,通过 clipPathAndPaint
,通过 canvas.clipPath
进行裁剪。 这里只是简单认识一下源码,更细节的东西这里就不展开了。
ClipPath 组件
的使用方式到这里就介绍完毕,那本文到这里就结束了,谢谢观看,明天见~