【剪裁 widget】Flutter ClipRect

大家好,我是 17。

用一个矩形去剪裁 child,矩形以外的部分不显示。通过和一些没有剪裁功能的 widget 合用,剪裁这些 widget 溢出的部分,还能高效的实现动画。

和布局 widget 不同,剪裁 widget 功能实现是在绘制阶段。所以 剪裁 widget 的 size 是不会变的,无论怎样剪裁。

因为实现是在绘制阶段,所以具体实现是在 paint 方法中调用 PaintingContext 类的 pushClipRect 方法进行剪裁。

 
  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。

image.png

class MyClipper extends CustomClipper<Rect> {
    
    
  
  Rect getClip(Size size) {
    
    
     return const Rect.fromLTWH(30, 40, 50, 50);
  }

  
  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 剪裁,还可以给 OverFlowBoxSizedOverFlowBox 等其它无法自己剪裁的 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});

  
  State<ClipRectApp> createState() => _ClipRectAppState();
}

class _ClipRectAppState extends State<ClipRectApp>
    with TickerProviderStateMixin {
    
    
  late AnimationController _sizeController;
  late Animation<double> _animation;
  
  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();
  }

  
  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;
  
  Rect getClip(Size size) {
    
    
    return Rect.fromCenter(
        center: const Offset(50, 50), width: _reclip.value, height: _reclip.value);
  }

  
  bool shouldReclip(covariant CustomClipper<Rect> oldClipper) {
    
    
    return false;
  }
}

clip 动画是很高效的,因为它省略了 build 的开销。

虽然 ClipRect 是 剪裁中最简单的一个,但它的应用却是最广泛的。自己能剪裁的 widget 实现的大都是矩形剪裁。比如 Stack

从源头上来说,其实大家都用的都是 PaintingContext 类的 pushClipRect 实现的矩形剪裁功能。

最后要注意的

如果你发现 ClipRect 没有效果从两个方向找原因

  1. clipper 参数是否正确
  2. 可能是 ClipRect 比 child 大,剪在了 child 之外。

猜你喜欢

转载自blog.csdn.net/m0_55635384/article/details/128929959