前言:
這是我參與8月更文挑戰的第 5 天,活動詳情檢視:8月更文挑戰。為應掘金的八月更文挑戰
,我準備在本月挑選 31
個以前沒有介紹過的元件,進行全面分析和屬性介紹。這些文章將來會作為 Flutter 元件集錄
的重要素材。希望可以堅持下去,你的支援將是我最大的動力~
- 1.【Flutter 元件集錄】NotificationListener| 8月更文挑戰
- 2.【Flutter 元件集錄】Dismissible| 8月更文挑戰
- 3.【Flutter 元件集錄】Switch 是怎樣煉成的| 8月更文挑戰
- 4.【Flutter 元件集錄】Scrollbar| 8月更文挑戰
- 5.【Flutter 元件集錄】ClipPath| 8月更文挑戰
[本文]
一、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 元件
的使用方式到這裡就介紹完畢,那本文到這裡就結束了,謝謝觀看,明天見~