原文作者:medium.com/@rahiche
发布时间:2018年10月9日 - 阅读6分钟

在计算机图形学中,将渲染限制在某一特定区域的行为被称为 "剪切"。剪辑区域被提供给画布,这样渲染引擎将只 "绘制 "定义区域内的像素。在该区域外 "绘制 "的任何东西都不会被渲染。
作为开发者,我们使用剪接来创建具有特殊效果的、看起来很棒的、自定义的用户界面。正如标题所说,这里我们将讨论如何在Flutter中进行剪切,让你在短时间内创建令人瞠目结舌的😲界面。
入门
让我们从创建一个赤裸裸的应用程序开始,在Scaffold的中心创建一个简单的200x200的红色矩形。
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
color: Colors.red,
width: 200.0,
height: 200.0,
),
),
);
}
}
定制剪切
CustomClipper是Flutter中剪裁的基础类,它被四个小部件使用。ClipRect , ClipRRect, ClipOval, ClipPath。
每一个都有一个定义的行为,所以如果你在ClipOval中包裹一个红色Container,结果是一个圆形。

改变Container的高度或宽度,它就会变成一个椭圆形。想想看,在Flutter中调整这样的椭圆是多么的快速和容易;您所要做的就是改变高度和/或宽度,并按Ctrl+\为热重新加载,看看您的变化在不到一秒钟的时间里是如何的。大一点,宽一点,不也许小一点......

现在,如果你想自定义剪辑的大小和位置,这就是CustomClipper的作用。让我们为我们的ClipOval Widget创建一个吧
class CustomRect extends CustomClipper<Rect>{
@override
Rect getClip(Size size) {
// TODO: implement getClip
}
@override
bool shouldReclip(CustomRect oldClipper) {
// TODO: implement shouldReclip
}
}
注意:在类的继承部分,对通用类型进行具体说明是非常重要的,你可以从 clipper 属性中看到你的 widget 需要什么。在这个例子中是Rect。
当你从CustomClipper类继承时,你需要覆盖两个方法。
第一个是getClip()。这为你提供了被剪切的RenderBox的大小,并要求你返回一个Rect。这个Rect将定义剪辑的大小和位置。顺便说一下:每个可见的Widget都有一个RenderBox)。
所以,如果我们返回这个:
Rect getClip(Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
return rect;
}
代码说明:使用fromLTRB构造函数,我们必须提供左、上、右、下的点。
这不会有任何改变,因为这里的 Rect 和 widget 的边界框是一样的。

但是如果你想只显示椭圆的一半呢?我们可以很容易做到,将矩形的左点改为-size.width,如果想看另一半,只需将右点改为size.width*2即可。

你需要覆盖的另一个方法是shouldReclip()。每当你提供一个新的对象(在本例中是CustomClipper<Rect>)被创建时,这个方法就会被调用,以与旧的对象进行比较,当它返回true时,getClip就会被调用,而不是反过来,因为当盒子的大小改变时,getClip方法也会被触发。
快速注意:测试时要确保shouldReclip返回true。如果没有,请明确地设置它,这样Hot Reload就会正确地改变剪辑区域。
各种剪切Widget
到目前为止,我们只介绍了一般的剪切,并举了使用ClipOval的例子。现在让我们来谈谈其他的剪裁部件,看看每个部件提供了什么。
ClipRect
用来从一个较大的图像中剪出一个矩形区域。例如,如果你只想要较大图像中的一小块矩形区域,你可以使用ClipRect。

Container(
child: Align(
alignment: Alignment.bottomRight,
heightFactor: 0.5,
widthFactor: 0.5,
child: Image.network(
"https://static.vinepair.com/wp-content/uploads/2017/03/darts-int.jpg"),
),
),
ClipRRect
这和ClipRect一样,都是圆角。请注意,你可以为每个角设置不同的曲率,而不是强迫你让四个角的半径都一样。在这个例子中,只有三个角被赋予了数值。我们要做的就是让左下角保持 "正方形",并且不给它分配任何值。默认情况下它是正方形的。

ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25.0),
topRight: Radius.circular(25.0),
bottomRight: Radius.circular(25.0),
),
child: Align(
alignment: Alignment.bottomRight,
heightFactor: 0.5,
widthFactor: 0.5,
child: Image.network(
"https://static.vinepair.com/wp-content/uploads/2017/03/darts-int.jpg"),
),
)
ClipPath
用来使用路径夹住一个自定义区域。如果你能用ClipRect、ClipRRect或ClipOval实现你想要的东西,那么你真的想用它们来代替。但是当你需要的时候,ClipPath可以成为你的救星。
比方说,你想剪辑一个三角形。

class TriangleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.lineTo(size.width, 0.0);
path.lineTo(size.width / 2, size.height);
path.close();
return path;
}
@override
bool shouldReclip(TriangleClipper oldClipper) => false;
}
clipBehavior
clipBehavior属性是新的,你会在Material Widgets中看到很多,因为几个月前讨论的突破性变化。有了这个变化,Flutter应用程序的速度快了30%,但这个突破性的变化的一部分是它禁用了对saveLayer的自动调用,它暴露了一个clipBehavior属性。有了它,你可以设置如何剪辑widget内容。大多数小组件的默认行为是Clip.none。
Clip.hardEdge

Clip.antiAlias

Clip.antiAliasWithSaveLayer.

正如你所看到的,antiAliasWithSaveLayer和antiAlias之间没有明显的区别,但是和hardEdge有明显的区别。在性能方面,它们之间有很大的区别,最快的是hardEdge,而antiAliasWithSaveLayer是最慢的。
渲染
在构建图层树的过程中,所有的剪切部件都会在PaintingContext中应用它们的剪切区域。当GPU将结果内容绘制到帧缓冲区时,我们添加的每一个图层都会增加其复杂性。我们总是建议尽量减少剪裁,因为这会给GPU增加每个图层的模板缓冲区,这样会增加很多渲染时间。最好将剪裁控制在需要的范围内,我们需要注意在动画制作过程中尽量少用。如果你想知道的话,那么请看Adam Barth在2016年介绍的Flutter的渲染管道。
结束语
与其他框架相比,Flutter的速度非常快,但这部分是因为Flutter团队提供了一个很好的API,它可以帮助你创建快速的应用程序;但有时会限制你使用会减慢你的应用程序速度的东西。正如Ian "Hixie" Hickson所说。
"我同意,做正确的剪裁是昂贵的。因此,我认为我们应该考虑是否可以重新设计框架,以完全避免剪接,除非明确要求。"
所以要记住费用,只有在真正需要的时候才使用剪裁,尽可能的使用优化后的小部件。
对我来说就是这样! 我希望你喜欢我的第一篇英文文章。我通过给几个编辑送百事可乐和多力多滋来贿赂他们,所以希望它能得到回报!
通过www.DeepL.com/Translator(免费版)翻译