Flutter如何将Canvas/CustomPainter内容保存为图片文件?
实现Flutter签名保存为图片的功能
我来帮你搞定签名保存的问题!你已经完成了签名绘制的核心部分,接下来只需要把CustomPaint里的绘制内容转换成图片字节数据,就能轻松存入数据库或者保存到本地了。下面是具体的实现步骤:
1. 导入必要的依赖包
首先需要添加几个dart内置的库,用来处理图片和字节数据:
import 'dart:ui'; import 'dart:typed_data';
如果之后需要把图片保存到本地文件,还可以额外导入path_provider包(记得先在pubspec.yaml里添加依赖);如果要存入SQLite数据库,需要导入sqflite包,不过核心的图片转换逻辑不需要这些,先搞定基础部分。
2. 添加签名转图片的核心方法
在_SignaturePadPage类里添加一个异步方法,把当前的签名转换成PNG格式的字节数组(这个字节数组就可以直接存入数据库了):
Future<Uint8List> _convertSignatureToImage() async { // 1. 创建PictureRecorder来记录Canvas的绘制内容 final recorder = PictureRecorder(); final canvas = Canvas(recorder); final painter = SignaturePainter(_points); // 2. 获取当前签名区域的尺寸(用页面的实际大小) final size = context.size!; // 让Painter在Recorder的Canvas上重新绘制一遍签名 painter.paint(canvas, size); // 3. 结束录制并生成Picture对象 final picture = recorder.endRecording(); // 把Picture转换成Image对象,指定宽高为当前页面的尺寸 final image = await picture.toImage(size.width.toInt(), size.height.toInt()); // 将Image转换成PNG格式的字节数据 final byteData = await image.toByteData(format: ImageByteFormat.png); return byteData!.buffer.asUint8List(); }
3. 添加保存按钮和保存逻辑
修改页面的build方法,把原来的Container改成Scaffold,这样可以在AppBar里添加保存按钮,同时处理保存逻辑:
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('签名板'), actions: [ IconButton( icon: const Icon(Icons.save), onPressed: _saveSignature, tooltip: '保存签名', ), ], ), body: Container( color: Colors.white, child: GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox referenceBox = context.findRenderObject() as RenderBox; Offset localPosition = referenceBox.globalToLocal(details.globalPosition); _points = List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) => _points.add(null), child: CustomPaint(painter: SignaturePainter(_points)), ), ), ); }
然后添加_saveSignature方法,处理保存的完整流程,包括判断是否有签名、转换图片、存入数据库等:
Future<void> _saveSignature() async { // 先判断用户是否已经签名 if (_points.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请先完成签名!')), ); return; } try { // 转换签名为PNG字节数组 final signatureBytes = await _convertSignatureToImage(); // --------------------------- // 这里就是你要存入数据库的部分了 // 比如用sqflite的话,代码大概是这样: // await yourDatabase.insert( // 'user_signatures', // {'signature_data': signatureBytes, 'created_at': DateTime.now().toIso8601String()}, // ); // --------------------------- // 保存成功后给用户提示 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('签名保存成功!')), ); } catch (e) { // 处理异常情况 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('保存失败:${e.toString()}')), ); } }
4. 完整代码整合
把所有部分整合起来,最终的代码大概是这样:
import 'package:flutter/material.dart'; import 'dart:ui'; import 'dart:typed_data'; class SignaturePadPage extends StatefulWidget { const SignaturePadPage({Key? key}) : super(key: key); @override _SignaturePadPage createState() => _SignaturePadPage(); } class _SignaturePadPage extends State<SignaturePadPage> { List<Offset?> _points = <Offset?>[]; Future<Uint8List> _convertSignatureToImage() async { final recorder = PictureRecorder(); final canvas = Canvas(recorder); final painter = SignaturePainter(_points); final size = context.size!; painter.paint(canvas, size); final picture = recorder.endRecording(); final image = await picture.toImage(size.width.toInt(), size.height.toInt()); final byteData = await image.toByteData(format: ImageByteFormat.png); return byteData!.buffer.asUint8List(); } Future<void> _saveSignature() async { if (_points.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请先完成签名!')), ); return; } try { final signatureBytes = await _convertSignatureToImage(); // 在这里添加存入数据库的逻辑 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('签名保存成功!')), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('保存失败:${e.toString()}')), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('签名板'), actions: [ IconButton( icon: const Icon(Icons.save), onPressed: _saveSignature, tooltip: '保存签名', ), ], ), body: Container( color: Colors.white, child: GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox referenceBox = context.findRenderObject() as RenderBox; Offset localPosition = referenceBox.globalToLocal(details.globalPosition); _points = List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) => _points.add(null), child: CustomPaint(painter: SignaturePainter(_points)), ), ), ); } } class SignaturePainter extends CustomPainter { final List<Offset?> points; SignaturePainter(this.points); @override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) { canvas.drawLine(points[i]!, points[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter other) => other.points != points; }
这样你就可以点击AppBar上的保存按钮,把签名转换成PNG字节数据,然后直接存入数据库了。如果之后需要展示签名,只需要把数据库里的字节数据取出来,用Image.memory(signatureBytes)就能显示啦!
内容的提问来源于stack exchange,提问作者Jus10
相关产品推荐
相关产品推荐

