Flutter中和绘制相关的对象有三个,分别是Canvas、Layer 和 Scene:
- Canvas:封装了Flutter Skia各种绘制指令,比如画线、画圆、画矩形等指令。
- Layer:分为容器类和绘制类两种;暂时可以理解为是绘制产物的载体,比如调用 Canvas 的绘制 API 后,相应的绘制产物被保存在 PictureLayer.picture 对象中。
- Scene:屏幕上将要要显示的元素。在上屏前,需要将Layer中保存的绘制产物关联到 Scene 上。
Flutter 绘制流程:
- 构建一个 Canvas,用于绘制;同时还需要创建一个绘制指令记录器,因为绘制指令最终是要传递给 Skia 的,而 Canvas 可能会连续发起多条绘制指令,指令记录器用于收集 Canvas 在一段时间内所有的绘制指令,因此Canvas 构造函数第一个参数必须传递一个 PictureRecorder 实例。
- Canvas 绘制完成后,通过 PictureRecorder 获取绘制产物,然后将其保存在 Layer 中。
- 构建 Scene 对象,将 layer 的绘制产物和 Scene 关联起来。
- 上屏;调用window.render API 将Scene上的绘制产物发送给GPU。
通过一个实例来演示整个绘制流程:
void main() {
//1.创建绘制记录器和Canvas
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
//2.在指定位置区域绘制。
var rect = Rect.fromLTWH(30, 200, 300,300 );
drawChessboard(canvas,rect); //画棋盘
drawPieces(canvas,rect);//画棋子
//3.创建layer,将绘制的产物保存在layer中
var pictureLayer = PictureLayer(rect);
//recorder.endRecording()获取绘制产物。
pictureLayer.picture = recorder.endRecording();
var rootLayer = OffsetLayer();
rootLayer.append(pictureLayer);
//4.上屏,将绘制的内容显示在屏幕上。
final SceneBuilder builder = SceneBuilder();
final Scene scene = rootLayer.buildScene(builder);
window.render(scene);
}
示例图:
PictureLayer 的绘制产物是 Picture,关于 Picture 有两点需要阐明:
- Picture 实际上是一系列的图形绘制操作指令,这一点可以参考 Picture 类源码的注释。
- Picture 要显示在屏幕上,必然会经过光栅化,随后Flutter会将光栅化后的位图信息缓存起来,也就是说同一个 Picture 对象,其绘制指令只会执行一次,执行完成后绘制的位图就会被缓存起来。
综合以上两点,可以看到 PictureLayer 的“绘制产物”一开始是一些列“绘图指令”,当第一次绘制完成后,位图信息就会被缓存,绘制指令也就不会再被执行了,所以这时“绘制产物”就是位图了。
Canvas绘制的位图转图片
既然 Picture 中保存的是绘制产物,那么它也应该能提供一个方法能将绘制产物导出,实际上,Picture有一个toImage方法,可以根据指定的大小导出Image。
//将图片导出为Uint8List
final Image image = await pictureLayer.picture.toImage();
final ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
print(pngBytes);