Flutter 离屏渲染

最近看了一下 Flutter 里离屏渲染和组件截图相关的东西,先记录一下思路。这个需求一般会出现在生成分享图、把某个 widget 转成图片、或者需要在界面外提前渲染内容的时候。

RepaintBoundary

Flutter 里最常见的截图方式是给目标 widget 外面包一层 RepaintBoundary,再通过 GlobalKey 拿到 RenderRepaintBoundary

大概流程是:

1
2
3
4
final boundary = key.currentContext?.findRenderObject()
as RenderRepaintBoundary?;
final image = await boundary?.toImage(pixelRatio: 3);
final bytes = await image?.toByteData(format: ImageByteFormat.png);

这样可以把当前已经渲染出来的 widget 转成图片。

渲染时机

比较容易踩坑的是:widget 必须已经完成布局和绘制,才能截图。

如果刚创建完就立刻调用 toImage(),可能会拿不到 context,或者图片是空的。一般要等一帧:

1
2
3
WidgetsBinding.instance.addPostFrameCallback((_) {
// capture image
});

如果内容里有网络图片,也要注意图片是否加载完成,否则截图出来可能是占位图。

真正的离屏

如果这个 widget 不在当前页面上,只是想在后台生成一张图,就麻烦一点。因为 Flutter 的 widget 需要经过 build、layout、paint 这一整套流程,不能只 new 一个 widget 就直接转图片。

常见做法是:

  • 放在页面里但不可见的位置,等渲染完成后截图。
  • 使用 Overlay 临时插入,截图后移除。
  • 自己搭一套离屏渲染 pipeline,但这个成本比较高,一般业务没必要。

前两种更适合业务场景,简单很多。

小结

目前我的理解是:

  • RepaintBoundary.toImage() 适合截取已经渲染出来的 widget。
  • 截图前要等布局和绘制完成。
  • 网络图片、字体、异步数据都会影响最终截图。
  • 真正离屏渲染成本比较高,业务里优先考虑 Overlay 或隐藏节点方案。

这块后面如果要做分享图生成,可以再整理一版完整实现。