You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:33:54