利用光影变化构建立体旋转效果

10,790 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

前言

之前我们提到了 CustomPaint erPaint 可以使用渐变(GradientShader)来填充绘制的图形,本篇我们来介绍使用图片填充,并且配合动画实现“立体”旋转效果,之所以给“立体”加上引号,是因为实际是通过填充图片自身的光影效果旋转后看起来像是立体效果一样。下面是实现的效果图。

光影立体旋转效果.gif

ImageShader 简介

ImageShader 的定义如下,我们来看看各个参数的用途。

  • image:用于填充的图像,是 Image 类,注意这个 Image 类定义在 dart:ui 库中,并不是我们用于构建图像组件的 Widget 下面的 Image 类。
  • tmx:图形在 x 轴的处理方式,即当被填充的宽度与图片宽度不匹配时,在横轴方向如何填充。
  • tmy:图形在y 轴的处理方式,即当被填充的高度与图片高度不匹配时,在纵轴方向如何填充。
  • matrix4:对填充图像的三维空间的平移、旋转等变换操作。
  • filterQuality:当图片尺寸和被填充图形的尺寸不一致时,采样的质量,有高(high)、中 (medium)、低(low)三类。
ImageShader(
  Image image, 
  TileMode tmx, 
  TileMode tmy, 
  Float64List matrix4, 
  {
    FilterQuality? filterQuality,
  }
)

tmxtmyTileMode 枚举,有以下几种取值:

  • clamp:图片不能完整填充图形时,使用最接近图形边缘的颜色进行填充,其实就是边缘的延伸。比如同时这是 tmx 和 tmy 都为 TileMode.clamp 的时候的效果图如下。

image.png

  • repeated:这个好理解,就是在各自方向重复。

image.png

  • decal:就是这个方向不做任何处理,使用透明填充,比如我们将上面的 tmy 改成 decal,就只会在 x 轴重复了。

image.png

  • mirror:顾名思义,就是镜像填充,下面是 tmxmirrortmyrepeated的效果图。

image.png

构建 ui.Image对象

要使用 ImageShader,我们首先要从图片资源中构建ui.Image 对象。这需要将图片文件按ByteData 二进制方式读取,在将二进制文件解码成为 ui.Image 对象。这个操作是异步的,因此在操作没完成前不能使用图像资源,我们设置了一个isImageLoaded布尔值标识图像是否加载完成。

final ByteData data = await rootBundle.load('images/beauty.jpg');
fillImage = await loadImage(Uint8List.view(data.buffer));

Future<ui.Image> loadImage(Uint8List img) async {
  final Completer<ui.Image> completer = Completer();
  ui.decodeImageFromList(img, (ui.Image img) {
    setState(() {
      isImageLoaded = true;
    });
    return completer.complete(img);
  });
  return completer.future;
}

使用 ImageShader 填充形状

使用 ImageShader 填充和 GradientShader 类似,按我们上面说的参数构建一个 ImageShader 对象就可以了,代码如下。注意,Paint 对象的填充方式必须是 fill,如果是空心的话是看不到效果的。

@override
void paint(Canvas canvas, Size size) {
  var paint = Paint();
  paint.style = PaintingStyle.fill;

  paint.shader = ImageShader(
    image,
    TileMode.mirror,
    TileMode.repeated,
    Matrix4.identity().storage,
    filterQuality: FilterQuality.high,
  );
  canvas.drawRect(Rect.fromLTRB(0, 0, size.width, size.height), paint);
}

立体旋转效果实现

立体效果需要利用填充图像的光影效果,因此我们需要找一张图片,比如明暗不同,或色彩不同的,这样旋转起来就会感觉是立体的。旋转的话使用 Matrix4的旋转变换就行。同时,为了让旋转过程展示的图像不固定,我们旋转的时候可以通过Matrix4的平移显示不同图形的区域。下面是我们用于测试的两张图片。

image.png

image.png

实现的代码如下所示,其中animationValue是动画过程中 Animation 对象的值,我们的动画时长设置为10秒。这里我们绘制的其实是一个圆形,只是因为填充图像在不停的旋转,且展示的图像不同就看起来有了立体效果。

void paint(Canvas canvas, Size size) {
    var paint = Paint();
    paint.style = PaintingStyle.fill;
    paint.shader = ImageShader(
      image,
      TileMode.mirror,
      TileMode.mirror,
      (Matrix4.identity()
            ..translate(5 * animationValue, 5 * animationValue)
            ..scaled(0.5)
            ..rotateZ(2 * pi * animationValue))
          .storage,
      filterQuality: FilterQuality.high,
    );
    Offset center = Offset(size.width / 2, size.height / 2);
    canvas.drawCircle(center, 200, paint);
}

下图是换了星球图片的运行效果,大家也可以调整 ImageShader 的参数看看其他参数的效果。 星球变换效果.gif

总结

源码已经提交至:绘图相关源码,文件名为:image_shader_demo.dart。本篇介绍了 Flutter 绘图使用 ImageShader 填充图形,并且利用 Matrix4的三维变换加上动画实现了立体旋转的动画效果。随着对 Flutter 绘图的深入,我们会发现绘图覆盖的面非常广,也能够实现更多有趣的效果。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!