Flutter手写签名首选!signature库实战指南

0 阅读8分钟

Flutter手写签名首选!signature库实战指南

在Flutter开发中,手写签名功能几乎是政务、金融、物流类App的必备需求——无论是电子合同签署、审批确认,还是用户签名留存,都需要一个稳定、流畅、功能全面的签名组件。

试过不少签名库,要么功能简陋(没有撤销/重做),要么性能不佳(低端设备卡顿),要么收费受限(商业库需授权),直到发现了 signature 这个实用库。

原生Flutter实现、MIT开源免费、支持压力感应、多格式导出,30天下载量达168.5k(pub.dev实时数据),持续活跃维护,能有效解决日常开发中的签名需求。今天就带大家从零到一掌握这个库,避坑的同时快速集成上线。

一、为什么首选signature库?

对比市面上其他签名库,signature的优势非常突出,尤其适合中小团队和个人开发者:

  • 原生Flutter实现,性能良好:不依赖原生插件,纯Dart编写,适配Android、iOS全机型,低端设备也能流畅渲染,无卡顿、无闪退,还做了针对性的性能优化,减少绘制延迟问题。
  • 功能全面,覆盖多数场景:支持压力感应、撤销/重做、PNG/SVG双格式导出、画布边界设置,还能通过保存的状态初始化签名,覆盖从基础到进阶的多数需求。
  • 开源免费,无额外限制:MIT协议,商业项目可直接使用,无水印、无功能阉割,无需付费授权,开发者可自由修改源码。

二、快速集成(10分钟上手)

集成步骤非常简单,分3步走,直接复制代码即可使用。

  • 持续维护,迭代稳定:最新版本6.3.0(2025年6月发布),长期更新迭代,修复已知bug、优化功能,社区活跃度高,遇到问题能较快找到解决方案。

2.1 引入依赖

在pubspec.yaml中添加最新版本依赖(当前最新6.3.0):

  • 易用性高,快速集成:API设计简洁,遵循Flutter标准开发模式,新手也能快速完成集成,无需复杂配置。
dependencies:
  flutter:
    sdk: flutter
  # 手写签名库,最新版本可去pub.dev查询
  signature: ^6.3.0

执行 flutter pub get 安装依赖,安装完成后即可开始使用。

2.2 基础使用示例(完整可运行)

一个包含“签名、清除、保存”核心功能的完整页面,直接复制到项目中即可运行:

import 'package:flutter/material.dart';
import 'package:signature/signature.dart';
import 'dart:typed_data';
import 'dart:ui' as ui;

class SignaturePage extends StatefulWidget {
  const SignaturePage({super.key});

  @override
  State<SignaturePage> createState() => _SignaturePageState();
}

class _SignaturePageState extends State<SignaturePage> {
  // 核心控制器:管理签名数据、笔触样式、撤销/重做等
  late final SignatureController _signatureController;
  // 保存导出的签名图片
  Uint8List? _signatureImage;

  @override
  void initState() {
    super.initState();
    // 初始化控制器,配置基础样式
    _signatureController = SignatureController(
      penColor: Colors.black, // 笔触颜色
      penStrokeWidth: 3.0, // 基础笔触宽度
      penStrokeWidthPressure: 6.0, // 压力最大宽度(压感生效时)
      exportPenColor: Colors.black, // 导出图片的笔触颜色
    );
  }

  @override
  void dispose() {
    // 释放控制器资源,避免内存泄漏
    _signatureController.dispose();
    super.dispose();
  }

  // 清除签名
  void _clearSignature() {
    setState(() {
      _signatureController.clear();
      _signatureImage = null;
    });
  }

  // 保存签名(导出为PNG)
  Future<void> _saveSignature() async {
    if (_signatureController.isNotEmpty) {
      // 导出为PNG,可指定宽度和高度
      final ui.Image image = await _signatureController.toImage(
        width: 800,
        height: 400,
      );
      final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
      if (byteData != null) {
        setState(() {
          _signatureImage = byteData.buffer.asUint8List();
        });
        // 这里可添加图片上传逻辑(如上传到服务器)
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("签名保存成功!")),
        );
      }
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("请先进行签名!")),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("手写签名示例"),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            // 签名组件(核心)
            Container(
              width: double.infinity,
              height: 300,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey, width: 1),
                borderRadius: BorderRadius.circular(8),
              ),
              // 签名画布,开启压力感应
              child: Signature(
                controller: _signatureController,
                dynamicPressureSupported: true, // 关键:开启压力感应
                backgroundColor: Colors.white,
              ),
            ),
            const SizedBox(height: 20),
            // 操作按钮(清除、保存)
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: _clearSignature,
                  child: const Row(
                    children: [Icon(Icons.clear), SizedBox(width: 8), Text("清除")],
                  ),
                ),
                ElevatedButton(
                  onPressed: _saveSignature,
                  child: const Row(
                    children: [Icon(Icons.save), SizedBox(width: 8), Text("保存")],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 20),
            // 预览导出的签名图片
            if (_signatureImage != null)
              Column(
                children: [
                  const Text("签名预览:"),
                  const SizedBox(height: 10),
                  Container(
                    width: 300,
                    height: 150,
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.grey, width: 1),
                    ),
                    child: Image.memory(_signatureImage!),
                  ),
                ],
              ),
          ],
        ),
      ),
    );
  }
}

三、核心功能详解(避坑重点)

signature库的功能看似简单,但有几个核心亮点和避坑点,建议掌握,避免踩坑。

3.1 压力感应(默认未开启)

不少开发者反馈“配置了压力感应却没效果”,核心原因是默认未开启,需手动设置 dynamicPressureSupported: true

关键配置:

// 1. 控制器配置(设置压力范围)
final _signatureController = SignatureController(
  penStrokeWidth: 3.0, // 基础宽度(无压力时)
  penStrokeWidthPressure: 6.0, // 最大宽度(最大压力时)
  // 压力感应的灵敏度可通过这两个参数调节,建议比例为1:2
);

// 2. 签名组件开启压感
Signature(
  controller: _signatureController,
  dynamicPressureSupported: true, // 必须显式开启,默认false
  // 其他配置...
)

注意事项:

  • 仅支持硬件具备压力感应的设备(如iPhone 6s+、部分高端Android平板、支持压感的触控笔设备)。
  • 普通手机屏幕无真实压感,会通过触摸面积模拟压感效果,效果略逊于真实压感设备。
  • 若压感无效果,优先检查是否开启 dynamicPressureSupported: true,其次调整两个宽度参数的差值。

3.2 撤销/重做(实用功能)

signature从5.0.0版本开始支持撤销/重做,6.0.0版本新增 canUndocanRedo 字段,可动态判断是否能执行对应操作,优化用户体验[superscript:4]。

示例代码(添加撤销/重做按钮):

// 撤销
void _undo() {
  if (_signatureController.canUndo) {
    _signatureController.undo();
  }
}

// 重做
void _redo() {
  if (_signatureController.canRedo) {
    _signatureController.redo();
  }
}

// 按钮布局(添加到操作栏)
ElevatedButton(
  onPressed: _undo,
  // 动态禁用:无可撤销操作时按钮置灰
  child: Row(
    children: [Icon(Icons.undo), SizedBox(width: 8), Text("撤销")],
  ),
),
ElevatedButton(
  onPressed: _redo,
  child: Row(
    children: [Icon(Icons.redo), SizedBox(width: 8), Text("重做")],
  ),
),

3.3 多格式导出(PNG+SVG)

支持两种常用导出格式,满足不同场景需求,6.2.0版本优化了SVG导出逻辑,解决了导出异常的问题[superscript:4]。

  • PNG导出:适合保存为图片、上传服务器,可指定宽度和高度,绘制内容自动居中。
  • SVG导出:矢量图格式,放大不失真,适合嵌入文档、PDF,可自定义导出参数。

SVG导出示例代码:

// 导出为SVG字符串
String svgString = _signatureController.toSvg(
  width: 800,
  height: 400,
  excludeTooClosePoints: true, // 排除过于接近的点,优化SVG体积
);

// 保存SVG文件(可结合file库实现)
// 这里示例仅获取SVG字符串,实际可根据需求写入文件或上传

3.4 画布边界与初始状态设置

这是signature的特色功能,可避免用户绘制超出画布范围,还能通过保存的状态初始化签名(如编辑已保存的签名)[superscript:1][superscript:2]。

关键配置:

// 1. 设置画布边界(避免绘制超出)
Signature(
  controller: _signatureController,
  // 设置画布内边距,绘制内容不会超出该范围
  padding: const EdgeInsets.all(16.0),
  backgroundColor: Colors.white,
),

// 2. 通过已保存的点初始化签名(如从服务器获取历史签名)
// 假设从服务器获取到保存的签名点列表(List<Offset>)
List<Offset> savedPoints = []; // 模拟从服务器获取的数据
final _signatureController = SignatureController(
  points: savedPoints, // 初始化签名状态
  penColor: Colors.black,
);

四、常见问题排查(避坑指南)

整理了开发中最常遇到的3个问题,快速排查,节省时间。

问题1:压力感应不生效

排查步骤(按优先级):

  1. 检查是否设置 dynamicPressureSupported: true(最常见遗漏)。
  2. 检查设备是否支持硬件压感(普通手机无真实压感,仅能模拟)。
  3. 调整 penStrokeWidthpenStrokeWidthPressure 的差值(差值越大,压感效果越明显)。

问题2:签名绘制超出画布范围

解决方案:

给Signature组件添加padding 属性,或给外层Container设置 clipBehavior: Clip.hardEdge,强制裁剪超出部分:

Container(
  width: double.infinity,
  height: 300,
  clipBehavior: Clip.hardEdge, // 强制裁剪超出部分
  decoration: BoxDecoration(
    border: Border.all(color: Colors.grey, width: 1),
  ),
  child: Signature(
    controller: _signatureController,
    padding: const EdgeInsets.all(16.0), // 内边距,进一步限制绘制范围
  ),
)

问题3:5.0.0+版本出现 “Incorrect use of ParentDataWidget” 错误

原因:5.0.0版本后,signature不再默认包裹Expanded组件,若在Row、Column、Flex中使用且未指定尺寸,会出现该错误[superscript:1][superscript:2]。

解决方案:

// 错误用法(未指定尺寸,在Column中使用)
Column(
  children: [
    Signature(controller: _signatureController), // 报错
  ],
)

// 正确用法:手动包裹Expanded
Column(
  children: [
    Expanded(
      child: Signature(controller: _signatureController),
    ),
  ],
)

// 或指定固定尺寸(无需包裹Expanded)
Signature(
  controller: _signatureController,
  width: 300,
  height: 200,
)

五、版本更新亮点(6.0.0+)

signature库的迭代非常稳定,6.0.0及以上版本有几个关键更新,建议使用最新版本:

  • 6.3.0:统一Web实现(本文不涉及Web,可忽略),提升导出性能和图片质量[superscript:4]。
  • 6.2.0:重新设计SVG导出逻辑,解决初始化签名后SVG导出异常的问题,优化SVG体积[superscript:4]。
  • 6.0.0:升级Flutter至3.29,新增 canUndocanRedo 字段,优化撤销/重做体验[superscript:4]。
  • 5.2.0:新增SVG导出功能,支持指定PNG导出尺寸[superscript:4]。

六、总结与选型建议

使用signature库开发了3个项目,从简单的签名留存到复杂的电子合同签署,它都能完美胜任,总结下来:

✅ 适合场景:政务、金融、物流、教育等需要手写签名的Flutter项目(Android/iOS)。

✅ 核心优势:功能全面、性能稳定、开源免费、易用性高,无需投入额外成本。

✅ 对比其他库:比flutter_signature_pad功能更全(支持撤销/重做、SVG导出),比syncfusion_flutter_signaturepad免费无限制,比hand_signature学习成本低。

如果你正在开发Flutter手写签名功能,signature库绝对是首选,按照本文的步骤集成,避坑又高效,10分钟就能实现生产级别的签名功能!

最后,附上官方地址:

如果本文对你有帮助,欢迎点赞、收藏、转发,评论区留言交流你的使用经验和踩坑经历~

(注:文档部分内容可能由 AI 生成)