关于 syncfusion-flutter-signature-pad
介绍 Syncfusion Flutter 的 SfSignaturePad,面向需要在 Flutter 界面中采集签名或绘图的开发者,涵盖输入、验证、定制、事件处理,以及导出为图像或原始数据。
主要功能
- 01支持触控和触控笔绘图
- 02定制笔画与背景
- 03最小笔画数验证
- 04导出 PNG、JPEG 和原始数据
- 05绘图回调与只读显示
适用场景
- 01在合同和表单中采集签名
- 02在审批流程中收集签名
- 03添加手写笔记或草图
- 04以只读方式显示已保存签名
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ syncfusion/ flutter-ui-components-skills --skill syncfusion-flutter-signature-pad安装后在 Claude Code 中输入/syncfusion-flutter-signature-pad确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/accessibility.mdadvanced-features.mdcallbacks.mdcustomization.mdgetting-started.mdoverview.mdsaving-exporting.md详细介绍
Syncfusion Flutter 签名板
本技能介绍 Syncfusion Flutter SfSignaturePad 组件,它为在 Flutter 应用中采集手写签名或绘图提供了优雅直观的方式。该组件支持触控和触控笔,绘图体验流畅,并具备强大的自定义和导出功能。
何时使用此技能
在以下情况下使用此技能:
- 采集电子签名,用于文档、表单或协议
- 在审批工作流中实现电子签名功能
- 在 PDF 表单或电子文档中创建签名栏
- 为合同、收据或法律表单构建文档签署界面
- 为笔记、草图或注释添加手写输入
- 在身份验证流程中实现签名验证界面
- 创建绘图区域,用于简单的素描或标记
- 使用基于签名的身份验证采集用户同意
- 构建需要手写签名的审批系统
- 在注册、结账或配送确认界面中添加签名板
组件概述
SfSignaturePad 是一个 Flutter 组件,可通过触控或触控笔输入采集流畅、高质量的签名或绘图。它提供可自定义的绘图区域,支持笔画自定义、背景图片、最小笔画验证以及多种导出格式等功能。
主要功能
- 流畅的绘图体验 - 针对触控和触控笔输入进行了优化,绘制曲线流畅
- 笔画自定义 - 控制笔画颜色、宽度和外观
- 背景支持 - 添加背景颜色或图片
- 最小笔画验证 - 确保采集到有意义的签名
- 多种导出格式 - 转换为图片(PNG、JPEG)或保存为原始数据
- 清除与重置 - 以编程方式清除签名板
- 只读模式 - 显示签名,但不允许修改
- 回调 - 处理绘制开始、绘制和绘制结束事件
- 透明背景 - 支持采集透明背景的签名
- 高质量输出 - 按所需分辨率导出签名
文档与导航指南
快速入门
📄 阅读: references/getting-started.md
- 安装和软件包设置
- SfSignaturePad 基本实现
- 软件包依赖项和导入
- 首次签名采集示例
- 快速设置指南
组件概述
📄 阅读: references/overview.md
- SfSignaturePad 组件概述和功能
- 何时使用 SignaturePad
- 核心功能和使用场景
- 组件架构
- 基本配置
自定义与样式设置
📄 阅读: references/customization.md
- 自定义笔画颜色
- 笔画宽度和最小/最大设置
- 背景颜色和图片
- 边框和容器样式
- 透明背景
- 自定义视觉外观
- 主题集成
保存与导出
📄 阅读: references/saving-exporting.md
- 将签名转换为图片(toImage 方法)
- 图片格式选项(PNG、JPEG)
- 分辨率和质量设置
- 将签名保存到存储空间
- 转换为 Uint8List
- 清除签名板
- 最小笔画数验证
- 导出模式和最佳实践
事件处理
📄 阅读: references/callbacks.md
- onDrawStart 回调
- onDraw 回调
- onDrawEnd 回调
- 处理签名事件
- 绘制期间进行验证
- 实时反馈模式
- 事件参数类型
高级功能
📄 阅读: references/advanced-features.md
- 用于显示签名的只读模式
- 背景图片支持
- 签名验证技巧
- 自定义绘制限制
- 与表单集成
- 签名比较
- 多签名工作流
- 撤销/重做实现模式
无障碍访问
📄 阅读: references/accessibility.md
- 屏幕阅读器支持
- 签名字段的语义标签
- 键盘无障碍访问注意事项
- 替代输入方式
- 符合 WCAG 标准
- 无障碍签名工作流
快速入门示例
基本签名板
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
class BasicSignaturePad extends StatelessWidget {
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Sign Here')),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
),
child: SfSignaturePad(
key: _signaturePadKey,
backgroundColor: Colors.white,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => _signaturePadKey.currentState!.clear(),
child: Text('Clear'),
),
ElevatedButton(
onPressed: _saveSignature,
child: Text('Save'),
),
],
),
],
),
);
}
Future<void> _saveSignature() async {
final image = await _signaturePadKey.currentState!.toImage();
// Process the image (save to file, upload, etc.)
}
}
自定义签名板
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
class CustomSignaturePad extends StatelessWidget {
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
@override
Widget build(BuildContext context) {
return SfSignaturePad(
key: _signaturePadKey,
backgroundColor: Colors.grey[100],
strokeColor: Colors.blue,
minimumStrokeWidth: 1.0,
maximumStrokeWidth: 4.0,
onDrawStart: () {
print('Started drawing');
return true;
},
onDraw: (offset, time) {
print('Drawing at: $offset');
},
onDrawEnd: () {
print('Finished drawing');
},
);
}
}
带验证功能的签名板
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
import 'dart:ui' as ui;
class ValidatedSignaturePad extends StatefulWidget {
@override
_ValidatedSignaturePadState createState() => _ValidatedSignaturePadState();
}
class _ValidatedSignaturePadState extends State<ValidatedSignaturePad> {
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
String _validationMessage = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Sign Document')),
body: Column(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.all(16),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
borderRadius: BorderRadius.circular(8),
),
child: SfSignaturePad(
key: _signaturePadKey,
backgroundColor: Colors.white,
strokeColor: Colors.black,
minimumStrokeWidth: 2.0,
maximumStrokeWidth: 4.0,
),
),
),
),
if (_validationMessage.isNotEmpty)
Padding(
padding: EdgeInsets.all(8),
child: Text(
_validationMessage,
style: TextStyle(color: Colors.red),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
OutlinedButton(
onPressed: _clearSignature,
child: Text('Clear'),
),
ElevatedButton(
onPressed: _saveSignature,
child: Text('Save Signature'),
),
],
),
),
],
),
);
}
void _clearSignature() {
setState(() {
_signaturePadKey.currentState!.clear();
_validationMessage = '';
});
}
Future<void> _saveSignature() async {
// Validate minimum stroke count
if (_signaturePadKey.currentState!.toImage == null) {
setState(() {
_validationMessage = 'Please provide a signature';
});
return;
}
try {
final ui.Image image = await _signaturePadKey.currentState!.toImage(
pixelRatio: 3.0,
);
// Convert to bytes
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final bytes = byteData!.buffer.asUint8List();
// Save or upload the signature
print('Signature saved: ${bytes.length} bytes');
setState(() {
_validationMessage = 'Signature saved successfully!';
});
// Navigate or perform next action
} catch (e) {
setState(() {
_validationMessage = 'Error saving signature: $e';
});
}
}
}
带背景图像的签名板
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
class SignatureWithBackground extends StatelessWidget {
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
image: DecorationImage(
image: AssetImage('assets/signature_background.png'),
fit: BoxFit.cover,
opacity: 0.3,
),
),
child: SfSignaturePad(
key: _signaturePadKey,
backgroundColor: Colors.transparent,
strokeColor: Colors.blue[900]!,
minimumStrokeWidth: 2.0,
maximumStrokeWidth: 5.0,
),
);
}
}
常见模式
模式 1:文档签署流程
class DocumentSigningFlow extends StatefulWidget {
@override
_DocumentSigningFlowState createState() => _DocumentSigningFlowState();
}
class _DocumentSigningFlowState extends State<DocumentSigningFlow> {
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
bool _hasSignature = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Sign Agreement')),
body: Column(
children: [
// Document preview
Expanded(
flex: 2,
child: Container(
padding: EdgeInsets.all(16),
child: Text('Agreement Terms...'),
),
),
// Signature section
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your Signature',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: SfSignaturePad(
key: _signaturePadKey,
backgroundColor: Colors.white,
strokeColor: Colors.black,
minimumStrokeWidth: 2.0,
maximumStrokeWidth: 4.0,
onDrawEnd: () {
setState(() => _hasSignature = true);
},
),
),
],
),
),
// Action buttons
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _clearSignature,
child: Text('Clear'),
),
),
SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _hasSignature ? _submitSignature : null,
child: Text('Submit'),
),
),
],
),
),
],
),
);
}
void _clearSignature() {
setState(() {
_signaturePadKey.currentState!.clear();
_hasSignature = false;
});
}
Future<void> _submitSignature() async {
final image = await _signaturePadKey.currentState!.toImage(pixelRatio: 3.0);
// Submit signature with document
Navigator.pop(context, image);
}
}
模式 2:收集多个签名
class MultipleSignaturesForm extends StatefulWidget {
@override
_MultipleSignaturesFormState createState() => _MultipleSignaturesFormState();
}
class _MultipleSignaturesFormState extends State<MultipleSignaturesForm> {
final GlobalKey<SfSignaturePadState> _applicantKey = GlobalKey();
final GlobalKey<SfSignaturePadState> _witnessKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Signatures Required')),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSignatureField(
'Applicant Signature',
_applicantKey,
),
SizedBox(height: 24),
_buildSignatureField(
'Witness Signature',
_witnessKey,
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _submitAllSignatures,
child: Text('Submit All'),
),
],
),
),
);
}
Widget _buildSignatureField(String label, GlobalKey<SfSignaturePadState> key) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Container(
height: 150,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: SfSignaturePad(
key: key,
backgroundColor: Colors.white,
strokeColor: Colors.blue[900]!,
),
),
TextButton(
onPressed: () => key.currentState!.clear(),
child: Text('Clear'),
),
],
);
}
Future<void> _submitAllSignatures() async {
final applicantImage = await _applicantKey.currentState!.toImage();
final witnessImage = await _witnessKey.currentState!.toImage();
// Submit both signatures
}
}
模式 3:只读签名显示
class SignatureDisplay extends StatefulWidget {
final Uint8List signatureData;
SignatureDisplay({required this.signatureData});
@override
_SignatureDisplayState createState() => _SignatureDisplayState();
}
class _SignatureDisplayState extends State<SignatureDisplay> {
@override
Widget build(BuildContext context) {
return Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: Image.memory(
widget.signatureData,
fit: BoxFit.contain,
),
);
}
}
模式 4:带实时反馈的签名
class InteractiveSignaturePad extends StatefulWidget {
@override
_InteractiveSignaturePadState createState() => _InteractiveSignaturePadState();
}
class _InteractiveSignaturePadState extends State<InteractiveSignaturePad> {
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
bool _isDrawing = false;
int _strokeCount = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
height: 300,
decoration: BoxDecoration(
border: Border.all(
color: _isDrawing ? Colors.blue : Colors.grey,
width: _isDrawing ? 2 : 1,
),
borderRadius: BorderRadius.circular(8),
),
child: SfSignaturePad(
key: _signaturePadKey,
backgroundColor: Colors.white,
strokeColor: Colors.black,
minimumStrokeWidth: 2.0,
maximumStrokeWidth: 4.0,
onDrawStart: () {
setState(() {
_isDrawing = true;
_strokeCount++;
});
return true;
},
onDrawEnd: () {
setState(() {
_isDrawing = false;
});
},
),
),
SizedBox(height: 8),
Text(
_isDrawing ? 'Drawing...' : 'Stroke count: $_strokeCount',
style: TextStyle(color: Colors.grey[600]),
),
],
);
}
}
关键属性
基本属性
backgroundColor- 签名板的背景颜色(Color)strokeColor- 签名笔迹的颜色(Color,默认值:Colors.black)minimumStrokeWidth- 笔迹的最小宽度(double,默认值:1.0)maximumStrokeWidth- 笔迹的最大宽度(double,默认值:4.0)
回调属性
onDrawStart- 开始绘制时的回调(SignatureOnDrawStartCallback → bool Function();返回 true 允许绘制,返回 false 取消)onDraw- 绘制期间触发的回调,包含偏移量和时间戳(DrawCallback)onDrawEnd- 绘制结束时的回调(VoidCallback)
状态方法(通过 GlobalKey)
clear()- 清除签名板上的所有笔迹toImage({double pixelRatio})- 将签名转换为 ui.ImagepixelRatio- 分辨率倍率(默认值:1.0;值越高,质量越好)
常见用例
- 合同签署 - 为法律文件和协议采集电子签名
- 送货确认 - 采集包裹收件人的签名
- 医疗同意书 - 采集患者的同意和授权签名
- 注册表单 - 在创建账户或注册时采集用户签名
- 结账流程 - 在完成付款或购买时采集签名
- 审批工作流 - 采集经理或授权人员的审批签名
- 收据确认 - 在电子收据上采集客户签名
- 工时表签署 - 采集员工的考勤或工时表审批签名
- 访客管理 - 在访客签到时采集签名
- 条款确认 - 通过签名确认接受条款和条件
最佳实践
验证
// Always validate before saving
Future<bool> validateSignature() async {
try {
final image = await _signaturePadKey.currentState!.toImage();
// Check if image has meaningful content
return true;
} catch (e) {
return false; // No signature drawn
}
}
图像质量
// Use higher pixel ratio for better quality
final image = await _signaturePadKey.currentState!.toImage(
pixelRatio: 3.0, // 3x resolution for high-quality signatures
);
用户体验
- 提供清晰的“清除”和“保存”按钮
- 在绘制期间显示视觉反馈(高亮边框)
- 清晰地显示验证消息
- 考虑添加“请在此处签名”提示或背景水印
- 允许用户在出错时重试
- 成功采集签名后显示确认信息
存储
import 'dart:ui' as ui;
import 'dart:typed_data';
Future<Uint8List> getSignatureBytes() async {
final ui.Image image = await _signaturePadKey.currentState!.toImage(
pixelRatio: 3.0,
);
final ByteData? byteData = await image.toByteData(
format: ui.ImageByteFormat.png,
);
return byteData!.buffer.asUint8List();
}
提示和注意事项
- 始终使用
GlobalKey<SfSignaturePadState>访问签名板方法 - 离开当前页面前调用
toImage(),以保留签名 - 较高的
pixelRatio值会生成更大的文件,但质量更好 - 验证失败时清空签名板,避免造成混淆
- 在触控设备和支持触控笔的设备上进行测试
- 考虑添加笔迹数量检查以进行验证
- 为笔迹和背景使用对比鲜明的颜色,确保清晰可见
- 提供足够的高度(150-300px),方便用户签名
评论
支持 Markdown