Flutter - 利用 ClipPath 實現任意形狀 Widget

Flutter筆記發表於2020-05-28

? 關於 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

我們來實現如下形狀(上面是原圖、下面是裁剪過的):

Flutter - 利用 ClipPath 實現任意形狀 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。

如有缺陷,希望大家提出,共同學習!?

Flutter - 利用 ClipPath 實現任意形狀 Widget

相關文章