用一个矩形去剪裁 child,矩形以外的部分不显示。通过和一些没有剪裁功能的 widget 合用,剪裁这些 widget 溢出的部分,还能高效的实现动画。
和布局 widget 不同,剪裁 widget 功能实现是在绘制阶段。所以 剪裁 widget 的 size 是不会变的,无论怎样剪裁。
因为实现是在绘制阶段,所以具体实现是在 paint 方法中调用 PaintingContext 类的 pushClipRect 方法进行剪裁。
@override void paint(PaintingContext context, Offset offset) { ... layer = context.pushClipRRect( needsCompositing, offset, _clip!.outerRect, _clip!, super.paint, clipBehavior: clipBehavior, oldLayer: layer as ClipRRectLayer?, ); } ... } 复制代码
相比于 ClipPath,ClipRect 是比较高效的,所以如果是要实现矩形剪裁,优先选用 ClipRect。
默认情况下,ClipRect 的剪裁路径是正好包含整个 child,只有溢出 child 的部分才会剪裁。我们可以指定 clipper 参数进行自定义裁剪。
举个例子,我们想裁剪出花朵的部分。图片大小为 100 x 100。
class MyClipper extends CustomClipper<Rect> { @override Rect getClip(Size size) { return const Rect.fromLTWH(30, 40, 50, 50); } @override bool shouldReclip(covariant CustomClipper<Rect> oldClipper) { return false; } } 复制代码
Center( child: ClipRect( clipper: MyClipper(), child: Image.network( 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e993af7d8ac142d5a5818e6a12249b2c~tplv-k3u1fbpfcp-watermark.image?', width: 100, height: 100, fit: BoxFit.fill, ), )) 复制代码
想要有裁剪效果, 自定义 clipper 是必须的,还好并不复杂。
ClipRect 的应用场景
我们知道有的 widget 是带 clip 参数的,有剪裁功能,但有的 wiget 没有,如果想剪裁溢出的部分怎么办呢。这就需要用到 ClipRect了。
裁剪 Align 溢出部分
Center( child: Column( children: [ Align( alignment: Alignment.topCenter, heightFactor: 0.5, child: Container( width: 100, height: 100, color: Colors.blue[300], ), ), const Text('IAM17 天天更新'), ], )) 复制代码
我们前面已经讲解过 Align Widget 了,正好复习一下。本例中 Align Widget 有 heightFactor 参数,所以它的高为 100 * 0.5 = 50
。虽然高为 50,但在绘制的时候,还会把 child 完整绘制出来,我们看到的 child 还是高 100。下面的文本 IAM17 天天更新
会接着在 50 高的位置显示出来。效果上就是文本覆盖了蓝色 box。
加上 ClipRect 就可以把多出来的部分 clip 掉。先看下效果
Center( child: Column( children: [ ClipRect( child: Align( alignment: Alignment.topCenter, heightFactor: 0.5, child: Container( width: 100, height: 100, color: Colors.blue[300], ), ), ), //省略字号等信息 const Text('IAM17 天天更新'), ], )) 复制代码
除了可以为 Align Widget 剪裁,还可以给 OverFlowBox,SizedOverFlowBox 等其它无法自己剪裁的 widget 进行剪裁。
clip 动画
在 css 中 clip 能做动画 ,flutter 中也是一样的,大同小异。
剪裁 widget 做动画的关键在于 clipper 参数,clipper 是 CustomClipper 类型 ,CustomClipper 的参数 reclip 可以用来做动画。
/// The clipper will update its clip whenever [reclip] notifies its listeners. const CustomClipper({Listenable? reclip}) : _reclip = reclip; 复制代码
还是举个例子吧,这样比较直观些。我就不用图片了,用矩形代替,直接就能画,重意而非重形。
贴出全部代码,方便大家自己试试。
import 'package:flutter/material.dart'; void main() => runApp(const ClipRectApp()); class ClipRectApp extends StatefulWidget { const ClipRectApp({super.key}); @override State<ClipRectApp> createState() => _ClipRectAppState(); } class _ClipRectAppState extends State<ClipRectApp> with TickerProviderStateMixin { late AnimationController _sizeController; late Animation<double> _animation; @override void initState() { _sizeController = AnimationController(vsync: this, duration: const Duration(seconds: 2)) ..repeat(); _animation = _sizeController.drive(Tween<double>(begin: 0.0, end: 100.0)); super.initState(); } @override Widget build(BuildContext context) { return Center( child: ClipRect( clipper: MyClipper( reclip: _animation, ), child: Container( width: 100, height: 100, color: Colors.blue[300], )), ); } } class MyClipper extends CustomClipper<Rect> { MyClipper({required Animation<double> reclip}) :_reclip=reclip, super(reclip: reclip); Animation<double> _reclip; @override Rect getClip(Size size) { return Rect.fromCenter( center: const Offset(50, 50), width: _reclip.value, height: _reclip.value); } @override bool shouldReclip(covariant CustomClipper<Rect> oldClipper) { return false; } } 复制代码
clip 动画是很高效的,因为它省略了 build 的开销。
虽然 ClipRect 是 剪裁中最简单的一个,但它的应用却是最广泛的。自己能剪裁的 widget 实现的大都是矩形剪裁。比如 Stack。
从源头上来说,其实大家都用的都是 PaintingContext 类的 pushClipRect 实现的矩形剪裁功能。
最后要注意的
如果你发现 ClipRect 没有效果从两个方向找原因
- clipper 参数是否正确
- 可能是 ClipRect 比 child 大,剪在了 child 之外。