Flutter - 利用 ClipPath 实现任意形状 Widget

4,918 阅读3分钟

🍭 关于 ClipPath

我们应该都使用过 ClipXXX 相关的组件, 来实现一些 圆角矩形/圆形形状十分的方便,那如果想要实现一些奇形怪状的 Widget,例如 五角星/圆弧形之类的,那就只能用 ClipPath了。

想要了解 ClipPath,还是直接去官网撸文档,介绍如下:

A widget that clips its child using a path.

Calls a callback on a delegate whenever the widget is to be painted. The callback returns a path and the widget prevents the child from painting outside the path.

Clipping to a path is expensive.

用 path 来剪切 child 的 widget。

每当要绘制小部件时,都会在委托上调用回调。回调函数返回一个路径,并且该 widget 可防止 child 在 path 外绘制。

裁剪 path 很昂贵。

总的来说,也就是按照路径来剪切子 widget,但是裁剪 path 很昂贵

🦾 来看一下怎么使用

关于如何使用,我们还是先来看一下他的构造函数:

const ClipPath({
  Key key,
  this.clipper, // final CustomClipper<Path> clipper;
  this.clipBehavior = Clip.antiAlias,
  Widget child,
}) : assert(clipBehavior != null),
super(key: key, child: child);

首先可以看到需要的参数其实就两个,一个是 clipper,另一个是 child

child 就是被 clipper 裁剪的组件,具体是啥自己来写就行了,剩下的就是 clipper

看一下 clipper 的源码:

/// CustomClipper
abstract class CustomClipper<T> {
  /// Creates a custom clipper.
  ///
  /// The clipper will update its clip whenever [reclip] notifies its listeners.
  const CustomClipper({ Listenable reclip }) : _reclip = reclip;

  final Listenable _reclip;

  /// 返回 clip 的说明 -> T
  T getClip(Size size);

  /// 是否重新 clip
  bool shouldReclip(covariant CustomClipper<T> oldClipper);

}

这里去掉了一些方法,只保留了我们需要重写的,其中最主要的就是 T getClip(Size size)

ClipPath 里传入的泛型为 <Path>,其实我们熟知的 ClipRect / ClipRRect / ClipOval 也就是对应着 CustomClipper<Rect> / CustomClipper<RRect> / CustomClipper<Rect> 而已。

所以在这里我们只需要定义好自己的 Path 就可以实现任意形状的 Widget 了。

😼 开始实现自定义形状的 Widget

我们来实现如下形状(上面是原图、下面是裁剪过的):

综上所述,只需要实现一个 CustomClipper<Path> 然后传入ClipPathclipper 参数即可。

代码如下:

class MyClipper extends CustomClipper<Path> {

  @override
  Path getClip(Size size) {
    Path path = Path();
    // 从 60,0 开始
    path.moveTo(60, 0);
    // 二阶贝塞尔曲线画弧
    path.quadraticBezierTo(0, 0, 0, 60);
    // 连接到底部
    path.lineTo(0, size.height / 1.2);
    // 三阶贝塞尔曲线画弧
    path.cubicTo(size.width / 4, size.height, size.width / 4 * 3, size.height / 1.5, size.width, size.height / 1.2);
    // 再连接回去
    path.lineTo(size.width, 60);
    // 再用二阶贝塞尔曲线画弧
    path.quadraticBezierTo(size.width - 60, 60, size.width - 60, 0);
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

逻辑就不说啦,都在注释里。

🦴 总结

因为ClipPath的消耗比较大,所以如果只是想裁剪个圆角之类的,还是推荐使用自带的 ClipRRect 之类的,他们的性能更好(官方文档所说)。

ClipPath 还有一个静态方法 ClipPath.shape(),这个具体就不说了,有感兴趣的可以去翻源码查看。

也可以看看 张风捷特烈的这篇文章 - 【Flutter高级玩法-shape】Path在手,天下我有。

这篇文章详细的讲解了 Path 的玩法,只有你想不到,没有它做不到!在最后也有讲解该静态方法。

代码已经提交到了 Github - 裁剪 Widget Demo。

如有缺陷,希望大家提出,共同学习!🤝