如图所示,图中的圆形缺口,需要我们自定义裁剪,才能实现。
Flutter自定义裁剪
裁剪,我们想到的是剪刀,实际上,Flutter的裁剪原理,和我们现实物理世界的剪刀是一样的,一定要想清楚,自己起点,终点,哪些是保留部分,哪些是裁掉的部分。
Flutter的自定义裁剪类CustomClipper
可以看到,CustomClipper并不是一个widget,是一个抽象类,因此,需要我们继承这个抽象类,实现抽象方法getclip,之后再配合ClipPath裁剪想要裁剪的widget
abstract class CustomClipper<T> extends Listenable {
/// Creates a custom clipper.
///
/// The clipper will update its clip whenever [reclip] notifies its listeners.
const CustomClipper({ Listenable? reclip }) : _reclip = reclip;
final Listenable? _reclip;
裁剪的实际代码
开篇中讲的效果,直接上代码
class MyClipper extends CustomClipper<Path> {
final double radius;
MyClipper(this.radius);
Path getClip(Size size) {
var path = new Path();
path.lineTo(0.0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0.0);
path.addOval(
Rect.fromCircle(center: Offset(0.0, size.height / 2), radius: radius));
path.addOval(Rect.fromCircle(
center: Offset(size.width, size.height / 2), radius: radius));
return path;
}
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
思路分析
前三句lineTo 从组件widget的左上角原点位置开始,向组件左下角移动,再画到右下角,再到右上角,至此,一个矩形组件的轮廓描绘出来。
接着,通过path.addOval来添加圆形缺口,看下这个addOval方法
/// Adds a new sub-path that consists of a curve that forms the
/// ellipse that fills the given rectangle.
///
/// To add a circle, pass an appropriate rectangle as `oval`. [Rect.fromCircle]
/// can be used to easily describe the circle's center [Offset] and radius.
void addOval(Rect oval) {
assert(_rectIsValid(oval));
_addOval(oval.left, oval.top, oval.right, oval.bottom);
}
注释翻译:
添加一条新的子路径,该路径由一条曲线组成,该曲线形成填充给定矩形的椭圆
简单理解就是在矩形上填充一个椭圆,通过Rect.fromCircle来添加圆形,Offset指定了圆心的位置。
通常我们想到的ClipOval是实现不了这个效果,因为这个会把要裁剪的widget,当作child,这个时候,要裁剪的组件整个就会被裁剪圆。
注意点
- 前三句lineTo实际才是关键,必须要把真个矩形轮廓拿到,才能进行下面的裁剪,这个也是最开始最难想到的点
- 实际上通过三阶贝塞尔曲线也可以实现类似的效果,但是实现起来非常复杂,需要计算控制点,每次只能裁剪半个圆,需要多次裁剪才能完成
- 裁剪效果会影响阴影效果,会把阴影效果裁剪掉,这个解决办法可以在完整代码中看下笔者的思路
完整代码
Widget build(BuildContext context) {
return ClipPath(
clipper: MyClipper(10.0),
child: Material(
elevation: 4.0,
shadowColor: Color(0x30E5E5E5),
color: Colors.transparent,
child: ClipPath(
clipper: MyClipper(12.0),
child: Card(
elevation: 0.0,
margin: const EdgeInsets.all(2.0),
child: _buildCardContent(),
),
),
),
);
}
通过Material作为底层,先做一次裁剪,上层在做裁剪,这样可以保留Material的阴影效果,避免被裁剪掉,切记,裁剪Clipper是可以把阴影裁剪掉的
总结
简单的一个裁剪效果,如果对你实现裁剪效果有帮助,那就是这篇文章的意义