跟着 Flutter 3.7 的更新, dart:ui 下多了 Picture.toImageSyncScene.toImageSync 这两个办法,和Picture.toImage 以及 Scene.toImage 不同的是 ,toImageSync 是一个同步履行办法,所以它不需求 await 等待,而调用 toImageSync 会直接返回一个 Image 的句柄,并在 Engine 后台会异步对这个 Image 进行光栅化处理。

前言

toImageSync 有什么用?不是有个 toImage 办法了,为什么要多一个 Sync 这样的同步办法?

  • 现在 toImageSync 最大的特色便是图画会在 GPU 中常驻 ,所以比照 toImage 生成的图画,它的制作速度会更快,并且能够重复运用,进步功率。

    toImage 生成的图画也能够完结 GPU 常驻,但现在没有未完结罢了。

  • toImageSync 是一个同步办法,在某些场景上弥补了 toImage 有必要是异步的缺乏。

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

toImageSync 的运用场景上,官方也列举了一些用途,例如:

  • 快速捕捉一张贵重的栅格化图片,用户支撑跨多帧重复运用
  • 应用在图片的多路过滤器上
  • 应用在自界说着色器上

具体在 Flutter Framework 里,现在 toImageSync 最直观的完结,便是被运用在 Android 默许的页面切换动画 ZoomPageTransitionsBuilder 上,满意于 toImageSync 的特性,Android 上的页面切换动画的功用,简直减少了帧光栅化一半的时刻,然后减少了掉帧和进步了刷新率。

当然,这是经过牺牲了一些其他特性来完结,后边咱们会讲到。

SnapshotWidget

前面说了 toImageSync 让 Android 的默许页面切换动画功用得到了大幅进步,那究竟是怎么完结的呢?这就要聊到 Flutter 3.7 里新增加的 SnapshotWidget

其实一开始 SnapshotWidget 是被界说为 RasterWidget ,从初始界说上看它的 Target 更大,可是终究在落地的时候,被简化处理为了 SnapshotWidget ,而从运用上看确实 Snapshot 更契合它的设定。

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

概念

SnapshotWidget 的作用是能够将 Child 变成的快照(ui.Image)然后替换它们进行显现,简而言之便是把子控件都变成一个快照图片,而 SnapshotWidget 得到快照的办法便是 Scene.toImageSync

那么到这里,你应该知道为什么 toImageSync 能够进步 Android 上的页面切换动画的功用了吧?由于 SnapshotWidget 会在页面跳转时把 Child 变成的快照,而 toImageSync 栅格化的图片还能够跨多帧重复运用。

那么问题来了,SnapshotWidget 既然是经过 toImageSync 将 Child 变成的快照(ui.Image)来进步功用,那么带来的副作用是什么?

答案是动画作用,由于子控件都变成了快照,所以假如 Child 控件带有动画作用,会出现“冻住”状况,更形象的比照如下图所示:

FadeUpwardsPageTransitionsBuilder ZoomPageTransitionsBuilder
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

默许情况下 Flutter 在 Android 上的页面切换作用运用的是 ZoomPageTransitionsBuilder ,而 ZoomPageTransitionsBuilder 里在页面切换时会开启 SnapshotWidget 的截图能力,所以能够看到,它在页面跳转时,比照 FadeUpwardsPageTransitionsBuilder 动图, ZoomPageTransitionsBuilder 的赤色方块和动画会停止。

由于动画很短,所以能够在代码里设置 timeDilation = 40.0;SchedulerBinding.resetEpoch 来大局减慢动画履行的速度,别的能够装备 MaterialApp ThemeData 下对应的 pageTransitionsTheme 来切换页面跳转作用。

所以在官方的界说中,SnapshotWidget 是用来协助履行一些简短的动画作用,比如一些 scale 、 skew 或许 blurs 动画在一些复杂的 child 构建上开销会很大,而运用 toImageSync 完结的 SnapshotWidget 能够依靠光栅缓存:

对于一些简短的动画,例如 ZoomPageTransitionsBuilder 的页面跳转, SnapshotWidget 会将页面内的 children 都转化为快照(ui.Image),虽然页面切换时会导致 child 动画“冻住”,可是实际页面切换时长很短,所以看不出什么反常,而带来的切换动画流畅度是清晰可见的

再举个更直观的例子,如下代码所示,运行后咱们能够看到一个旋转的 logo 在屏幕上随机翻滚,这里分别运用了 AnimatedSlideAnimatedRotation 履行移动和旋滚动画。

Timer.periodic(const Duration(seconds: 2), (timer) {
  final random = Random();
  x = random.nextInt(6) - 3;
  y = random.nextInt(6) - 3;
  r = random.nextDouble() * 2 * pi;
  setState(() {});
});
AnimatedSlide(
  offset: Offset(x.floorToDouble(), y.floorToDouble()),
  duration: Duration(milliseconds: 1500),
  curve: Curves.easeInOut,
  child: AnimatedRotation(
    turns: r,
    duration: Duration(milliseconds: 1500),
    child: Image.asset(
      'static/test_logo.png',
      width: 100,
      height: 100,
    ),
  ),
)

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

假如这时候在 AnimatedRotation 上层加多一个 SnapshotWidget ,并且打开 allowSnapshotting ,能够看到此时 logo 不再滚动,由于整个 child 已经被转化为快照(ui.Image)。

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

所以 SnapshotWidget 不适用于子控件还需求继续动画或有交互呼应的当地,例如轮播图。

运用

如之前的代码所示,运用 SnapshotWidget 也相对简略,你只需求装备 SnapshotController ,然后经过 allowSnapshotting 控制子控件是否烘托为快照即可。

 controller.allowSnapshotting = true;

SnapshotWidget 在捕获快照时,会生成一个全新的 OffsetLayerPaintingContext,然后经过 super.paint 完结内容捕获(这也是为什么不支撑 PlatformView 的原因之一),之后经过 toImageSync 得到完整的快照(ui.Image)数据,并交给 SnapshotPainter 进行制作。

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

所以 SnapshotWidget 完结图片制作会需求一个 SnapshotPainter ,默许它是经过内置的 _DefaultSnapshotPainter 完结,当然咱们也能够自界说完结 SnapshotPainter 来完结自界说逻辑。

从完结上看,SnapshotPainter 用来制作子控件快照的接口,正如上面代码所示,会根据 child 是否支撑捕获(_childRaster == null),然后挑选调用 paintpaintSnapshot 来完结制作。

别的,现在受制于 toImageSync 的底层完结, SnapshotWidget 无法捕获 PlatformView 子控件,假如遇到 PlatformView,SnapshotWidget 会根据 SnapshotMode 来决议它的行为:

normal 默许行为,假如遇到无法捕获快照的子控件,直接 thrown
permissive 宽松行为,遇到无法捕获快照的子控件,运用未快照的子目标烘托
forced 强制行为,遇到无法捕获快照的子控件直接忽略

别的 SnapshotPainter 能够经过调用 notifyListeners 触发 SnapshotWidget 运用相同的光栅进行重绘,简略来说便是:

你能够在不需求从头生成新快照的情况下,对当然快照进行一些缩放、含糊、旋转等作用,这对功用会有很大进步

所以在 SnapshotPainter 里主要需求完结的是 paintpaintSnapshot 两个办法:

  • paintSnapshot 是制作 child 快照时会被调用

  • paint 办法里主要是经过 painter (对应 super.paint)这个 Callback 制作 child ,当快照被禁用或许 permissive 形式下遭受 PlatformView 时会调用此办法

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

举个例子,如下代码所示,在 paintSnapshot 办法里,经过调整 Paint ..color ,能够在前面的小 Logo 快照上增加透明度作用:

class TestPainter extends SnapshotPainter {
  final Animation<double> animation;
  TestPainter({
    required this.animation,
  });
  @override
  void paint(PaintingContext context, ui.Offset offset, Size size,
      PaintingContextCallback painter) {}
  @override
  void paintSnapshot(PaintingContext context, Offset offset, Size size,
      ui.Image image, Size sourceSize, double pixelRatio) {
    final Rect src = Rect.fromLTWH(0, 0, sourceSize.width, sourceSize.height);
    final Rect dst =
    Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height);
    final Paint paint = Paint()
      ..color = Color.fromRGBO(0, 0, 0, animation.value)
      ..filterQuality = FilterQuality.low;
    context.canvas.drawImageRect(image, src, dst, paint);
  }
  @override
  void dispose() {
    super.dispose();
  }
  @override
  bool shouldRepaint(covariant TestPainter oldDelegate) {
    return oldDelegate.animation.value != animation.value;
  }
}

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

其实还能够把移动的动画部分挪到 paintSnapshot 里,然后经过对 animation 的状况进行管理,然后经过 notifyListeners 直接更新快照制作,这样在功用上会更有优势,Android 上的 ZoomPageTransitionsBuilder 便是相似完结。

  animation.addListener(notifyListeners);
  animation.addStatusListener(_onStatusChange);
  void _onStatusChange(_) {
    notifyListeners();
  }
  @override
  void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio) {
    _drawMove(context, offset, size);
  }
  @override
  void paint(PaintingContext context, ui.Offset offset, Size size, PaintingContextCallback painter) {
    switch (animation.status) {
      case AnimationStatus.completed:
      case AnimationStatus.dismissed:
        return painter(context, offset);
      case AnimationStatus.forward:
      case AnimationStatus.reverse:
    }
    ....
  }

更多具体能够参阅体系 ZoomPageTransitionsBuilder 里的代码完结。

拓宽探究

其实除了 SnapshotWidget 之外,RepaintBoundary 也支撑了 toImageSync , 由于 toImageSync 获取到的是 GPU 中的常驻数据,所以在完结相似控件截图和高亮指引等场景制作上,理论上应该能够得到更好的功用预期。

final RenderRepaintBoundary boundary =
    globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final ui.Image image = boundary.toImageSync();

除此之外,dart:ui 里的 Scene_Image 目标其实都是 NativeFieldWrapperClass1 ,曾经咱们解释过:NativeFieldWrapperClass1 便是它的逻辑是由不同渠道的 Engine 区分完结

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

所以假如你直接在 flutter/bin/cache/pkg/sky_engine/lib/ui/compositing.dart 下去断点 toImageSync 是无法成功履行到断点位置的,由于它的实在完结在对应渠道的 Engine 完结。

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

别的,前面咱们一直说 toImageSync 比照 toImage 是 GPU 常驻,那它们的差异在哪里?从上图咱们就能够看出:

  • toImageSync 履行了 Scene:RasterizeToImage 并返回 Dart_Null 句柄
  • toImage 履行了 Picture:RasterizeLayerTreeToImage 并直接返回

简略打开来说,便是:

  • toImageSync 终究是经过 SkImage::MakeFromTexture 经过纹路得到一个 GPU SkImage 图片
  • toImage 是经过 makeImageSnapshotmakeRasterImage 生成 SkImagemakeRasterImage 是一个仿制图画到 CPU 内存的操作。
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

其实一开始 toImageSync 是被指令为 toGpuImage ,可是为了更形象通用,最终才修改为 toImageSync

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

toImageSync 等相关功用的落地能够说相同历经了漫长的评论,关于是否供给这样一个 API 到终究落地,其履行难度一点点不比 background isolate 简略,比如:是否界说反常场景,遇到错误是否需求在Framwork 层消化,是否真的需求这样的接口来进步功用等等。

Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?
Flutter 3.7 之快速理解 toImageSync 是什么?能做什么?

toImageSync 等相关功用终究能落地,其间最重要的一点我认为是:

toGoulmage gives the framework the ability to take performance into their own hands, which is important given that our priorities don’t always line up.

最终

toImageSync 仅仅一个简略的 API ,可是它的背后阅历了许多故事,一起 toImageSync 和它对应的封装 SnapshotWidget ,终究的目的便是进步 Flutter 运行的功用。

或许现在对于你来说 toImageSync 并不是有必要的,乃至 SnapshotWidget 看起来也很鸡肋,可是一旦你需求处理复杂的制作场景时, toImageSync 便是你必不可少的菜刀。