mcpField
S
syncfusion-flutter-signature-pad

syncfusion-flutter-signature-pad

github.com/syncfusion开发250 次安装16 周前更新
代码仓库

关于 syncfusion-flutter-signature-pad

介绍 Syncfusion Flutter 的 SfSignaturePad,面向需要在 Flutter 界面中采集签名或绘图的开发者,涵盖输入、验证、定制、事件处理,以及导出为图像或原始数据。

主要功能

  1. 01支持触控和触控笔绘图
  2. 02定制笔画与背景
  3. 03最小笔画数验证
  4. 04导出 PNG、JPEG 和原始数据
  5. 05绘图回调与只读显示

适用场景

  1. 01在合同和表单中采集签名
  2. 02在审批流程中收集签名
  3. 03添加手写笔记或草图
  4. 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.md
advanced-features.md
callbacks.md
customization.md
getting-started.md
overview.md
saving-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.Image
    • pixelRatio - 分辨率倍率(默认值:1.0;值越高,质量越好)

常见用例

  1. 合同签署 - 为法律文件和协议采集电子签名
  2. 送货确认 - 采集包裹收件人的签名
  3. 医疗同意书 - 采集患者的同意和授权签名
  4. 注册表单 - 在创建账户或注册时采集用户签名
  5. 结账流程 - 在完成付款或购买时采集签名
  6. 审批工作流 - 采集经理或授权人员的审批签名
  7. 收据确认 - 在电子收据上采集客户签名
  8. 工时表签署 - 采集员工的考勤或工时表审批签名
  9. 访客管理 - 在访客签到时采集签名
  10. 条款确认 - 通过签名确认接受条款和条件

最佳实践

验证
// 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