前语
日月如梭,《Flutter 制作指南 – 妙笔生花》 转眼间现已发布两年半了,不知道各位练习得怎么样。有不少朋友问过怎么将 Canvas 制作的内容保存为图片,最近在做的东西刚好触及了这块,经过本文来共享一下。
说到保存图片,很多人或许会想到 RepaintBoundary
, 但它运用起来有点繁琐,一起也存在一些局限性。并且 Canvas 有更灵敏的生成图片办法,没有必要运用 RepaintBoundary
,下面一起来看看吧!
1. 自己创立 Canvas 目标
如果我问:
怎么得到 Canvas 目标,来进行制作操作?
或许绝大多数朋友都知道承继自 CustomPainter
,在 paint
回调中获取 Canvas 目标,它是在 Flutter 框架中创立的。你有没有想过,我可不能够自己创立一个 Canvas 目标呢?
实例化目标,最重要的是 结构办法,能够进入 Canvas
源码中瞄一眼。 能够看到它有一个结构办法,说明答应外界进行实例化。其中有必要传入一个 PictureRecorder
目标;
而 PictureRecorder
类有无参结构,这样就为咱们自己创立 Canvas 目标提供了或许性,代码如下:
void createCanvas(){
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
}
2. 得到 Picture 目标
或许很少人知道 Picture 目标,但它在框架层烘托机制中的出场率挺高,看过烘托小册的应该知道它。 经过PictureRecorder#endRecording
能够得到 Picture 目标,期间 Canvas
制作的内容将被 记录 到 Picture
中。比方下面制作了 100*100
尺度的蓝色盒子:
import 'dart:ui' as ui;
void createCanvas(){
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
Size boxSize = const Size(100, 100);
canvas.drawRect(Offset.zero&boxSize, Paint()..color=Colors.blue);
Picture picture = recorder.endRecording();
}
生成图片的核心办法是 Picture
的 toImage
办法,能够异步生成 ui.Image
目标:
3. 存储图片
有了 ui.Image
目标,就能够得到图片的字节数组,存入到文件中即可:这里在 Windows 中测试的,其他渠道也是相似。
import 'dart:ui' as ui;
void createCanvas() async{
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
Size boxSize = const Size(100, 100);
canvas.drawRect(Offset.zero&boxSize, Paint()..color=Colors.blue);
Picture picture = recorder.endRecording();
ui.Image image = await picture.toImage(boxSize.width.toInt(), boxSize.height.toInt());
// 获取字节,存入文件
ByteData? byteData = await image.toByteData(format:ImageByteFormat.png );
if(byteData!=null){
File file = File(r"E:\Temp\desk\box.png");
file.writeAsBytes(byteData.buffer.asUint8List());
}
}
经过这种办法制作生成图片,优点是能够自在操作 Canvas 的制作内容,并且必依赖于组件,只要在任何需要的当地触发办法即可。如下所示,在 FloatingActionButton
中触发 createCanvas
办法即可保存图片。
别的,这种办法还有另一个很大的优势,能够经过 canvas 的操作让图片扩大,取得非常大尺度的图片。比方将制作内容扩大 100 倍存储,这是 RepaintBoundary 无法做到的。还有 RepaintBoundary
无法获取到没有烘托到的内容,无法做到长屏图片的生成。而把握 Canvas 咱们能够控制一切。
4. CustomPainter 与 PictureRecorder
上面说了,能够在不显示的情况下将画板内容保存为图片。但很多时分,咱们会经过 CustomPainter 先画出来,这时分怎么经过保存 CustomPainter 类中制作的内容呢?比方这里也就就有一个 ShapePainter
,是一个 100*100
尺度的蓝色盒子,上面放着半径 40 的红色边线圆:
现在想要点击按钮,将 ShapePainter
的制作内容保存起来,该怎么办呢?在生成图片的当地再写一遍制作逻辑吗?
class ShapePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()..color = Colors.blue;
canvas.drawRect(Offset.zero & size,paint );
paint..style=PaintingStyle.stroke..color=Colors.redAccent..strokeWidth=2;
canvas.drawCircle(Offset(size.width/2 , size.height/2),40, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
其中细心考虑一下,上图红框的区域 canvas 的操作在 Picture 中留下 "痕迹"
,而 CustomPainter
正好有个办法,能够操作一个 canvas 目标,于是乎:将 ShapePainter
作为成员变量,能够经过 _painter#paint
办法去操作自己创立的 Canvas 目标,从而在 Picture 中留下对应的 "痕迹"
。
// 作为成员变量
ShapePainter _painter = ShapePainter();
void createCanvas() async {
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
Size boxSize = const Size(100, 100);
// 经过 _painter 目标操作 canvas
_painter.paint(canvas, boxSize);
Picture picture = recorder.endRecording();
//略同...
}
这里想说明一点: paint
虽然是 ShapePainter
类中覆写的办法,它会在框架中某些机遇被触发(俗称回调)。但它自身仍是该目标的成员办法,能够经过目标来调用。不要固化思想,觉得回调的办法一定要等着被底层调用。
这样,任何的 CustomPainter
完结类都能够很容易地经过 PictureRecorder->Canvas->Picture 这套组合拳生成图片来保存:
5. 制作永无止境
制作自身是一个发明过程,而发明是没有上限的。将 Canvas 保存为图片,能够让你发明的、在界面上的出现物,转化为可传输的图片资源。让它能够脱离 Flutter 制作系统,经过图片展现在任何设备屏幕上。经过 Canvas 制作能够完结很多事:
比方,经过 制作+手势 能够操作图片,进行裁剪图像,最终根据矩形区域运用上面的办法,将选取的局部图片制作到自己创立的画板上,保存为图片。
别的,截图、图片编辑器也少不了制作的技术,箭头、根本图形、文字都是在图片之上制作的内容。最终保存图片时也都能够运用上面的办法。
最终,比方的这种共享的卡片图片,也能够经过制作来处理,共享时本质上是共享图片。保存图片也是上面 PictureRecorder->Canvas->Picture 这套组合拳。
总的来看, Canvas 保存成图片的过程非常简单,也就十几行代码。但关于很多人来说,不知道就做不出来,让更多人知道一些量力而行常识,这也是共享的意义地点。期望本文能够在你想要保存制作成图片时,对你有所帮助。那本文就到这里,谢谢观看 ~
本文正在参与「金石方案」