Flutter:签名板封装

签名板

依赖安装

cpp 复制代码
  # 图片处理
  image: ^4.1.3
  # 签名
  signature: ^5.4.1

封装组件

js 复制代码
import 'dart:typed_data';
import 'package:dogex/common/index.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:signature/signature.dart';
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class SignaturePad {
  /// 显示签名板
  /// [backgroundColor] 签名区域背景色
  /// [titleColor] 标题文字颜色
  /// [penColor] 签名笔颜色
  /// [onConfirm] 确认回调,返回签名图片的PNG字节数据
  static Future<void> show({
    Color backgroundColor = Colors.white,
    Color titleColor = Colors.white,
    Color penColor = Colors.black,
    required Function(Uint8List?) onConfirm,
    bool convertToJpg = false,
    int jpgQuality = 90,
  }) async {
    final SignatureController controller = SignatureController(
      penStrokeWidth: 3,
      penColor: penColor,
      exportBackgroundColor: backgroundColor,
    );

    await Get.bottomSheet(
      PopScope(
        canPop: false, // 禁止通过返回键或滑动关闭
        child: Container(
          height: 800.w,
          decoration: BoxDecoration(
            color: AppTheme.color2223,
            borderRadius: BorderRadius.vertical(
              top: Radius.circular(30.w),
            ),
          ),
          child: Column(
            children: [
              // 顶部标题栏
              Container(
                padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 20.w),
                child: Column(
                  children: [
                    // 标题和清除按钮
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        TextWidget.body(
                          '签署',
                          size: 32.sp,
                          color: titleColor,
                          weight: FontWeight.w600,
                        ),
                        GestureDetector(
                          onTap: () {
                            controller.clear();
                          },
                          child: Container(
                            padding: EdgeInsets.all(10.w),
                            child: TextWidget.body(
                              '清除',
                              size: 28.sp,
                              color: titleColor,
                            ),
                          ),
                        ),
                      ],
                    ),
                    SizedBox(height: 10.w),
                    // 提示文字
                    TextWidget.body(
                      '请在下方区域签名',
                      size: 24.sp,
                      color: AppTheme.color999,
                    ),
                  ],
                ),
              ),
              // 签名区域
              Expanded(
                child: Container(
                  margin: EdgeInsets.symmetric(horizontal: 30.w),
                  decoration: BoxDecoration(
                    color: backgroundColor,
                    borderRadius: BorderRadius.circular(20.w),
                  ),
                  child: Signature(
                    controller: controller,
                    // backgroundColor: backgroundColor,
                  ),
                ),
              ),
              // 底部按钮
              Container(
                padding: EdgeInsets.all(30.w),
                child: Row(
                  children: [
                    // 取消按钮
                    Expanded(
                      child: Container(
                        height: 90.w,
                        decoration: BoxDecoration(
                          color: AppTheme.blockBgColor,
                          borderRadius: BorderRadius.circular(45.w),
                        ),
                        child: TextButton(
                          onPressed: () {
                            Get.back();
                          },
                          style: TextButton.styleFrom(
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(45.w),
                            ),
                          ),
                          child: TextWidget.body(
                            '取消',
                            size: 28.sp,
                            color: titleColor,
                          ),
                        ),
                      ),
                    ),
                    SizedBox(width: 20.w),
                    // 确认按钮
                    Expanded(
                      child: Container(
                        height: 90.w,
                        decoration: BoxDecoration(
                          color: AppTheme.primaryBlue,
                          borderRadius: BorderRadius.circular(45.w),
                        ),
                        child: TextButton(
                          onPressed: () async {
                            if (controller.isEmpty) {
                              Loading.toast('请签名后再确认');
                              return;
                            }
                            
                            final pngData = await controller.toPngBytes();
                            
                            if (pngData != null) {
                              if (convertToJpg) {
                                try {
                                  // 转换为JPG格式
                                  final jpgData = await _convertPngToJpg(pngData, jpgQuality);
                                  Get.back();
                                  onConfirm(jpgData);
                                } catch (e) {
                                  Loading.toast('图片转换失败');
                                  Get.back();
                                  onConfirm(pngData); // 转换失败时返回原始PNG数据
                                }
                              } else {
                                Get.back();
                                onConfirm(pngData);
                              }
                            } else {
                              Loading.toast('签名生成失败');
                            }
                          },
                          style: TextButton.styleFrom(
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(45.w),
                            ),
                          ),
                          child: TextWidget.body(
                            '确认',
                            size: 28.sp,
                            color: Colors.white,
                            weight: FontWeight.w600,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
      isScrollControlled: true,
      enableDrag: false, // 禁止拖拽关闭
      isDismissible: false, // 禁止点击外部关闭
    );

    // 释放控制器
    controller.dispose();
  }

  /// 将PNG字节数据转换为JPG格式
  static Future<Uint8List> _convertPngToJpg(Uint8List pngData, int quality) async {
    // 使用image包解码PNG
    final pngImage = img.decodePng(pngData);
    if (pngImage == null) {
      throw Exception('无法解码PNG图片');
    }
    
    // 转换为JPG格式
    final jpgData = img.encodeJpg(pngImage, quality: quality);
    return Uint8List.fromList(jpgData);
  }

  /// 保存签名图片到本地文件
  static Future<String?> saveSignatureToFile(Uint8List imageData, {bool isJpg = false}) async {
    try {
      final directory = await getApplicationDocumentsDirectory();
      final timestamp = DateTime.now().millisecondsSinceEpoch;
      final extension = isJpg ? 'jpg' : 'png';
      final path = '${directory.path}/signature_$timestamp.$extension';
      
      final file = File(path);
      await file.writeAsBytes(imageData);
      return path;
    } catch (e) {
      print('保存签名图片失败: $e');
      return null;
    }
  }
} 

页面调用

js 复制代码
  // 弹出签名框
  void onSignPopup() {
    SignaturePad.show(
      backgroundColor: Colors.white,
      titleColor: AppTheme.colorfff,
      penColor: Colors.black,
      convertToJpg: true, // 转换为JPG格式
      jpgQuality: 90, // JPG质量
      onConfirm: (data) async {
        if (data != null) {
          // 保存签名图片到本地文件
          final filePath = await SignaturePad.saveSignatureToFile(
            data, 
            isJpg: true
          );
          
          if (filePath != null) {
            // 保存到相册
            // 这里将输入图片的临时路径
            print('签名保存成功: $filePath');
          } else {
            Loading.toast('签名保存失败');
          }
        }
      },
    );
  }
相关推荐
qq_5298353512 分钟前
ThreadLocal内存泄漏 强引用vs弱引用
java·开发语言·jvm
景彡先生16 分钟前
C++并行计算:OpenMP与MPI全解析
开发语言·c++
LuciferHuang1 小时前
震惊!三万star开源项目竟有致命Bug?
前端·javascript·debug
GISer_Jing1 小时前
前端实习总结——案例与大纲
前端·javascript
量子联盟2 小时前
原创-基于 PHP 和 MySQL 的证书管理系统,免费开源
开发语言·mysql·php
姑苏洛言2 小时前
编写产品需求文档:黄历日历小程序
前端·javascript·后端
知识分享小能手3 小时前
Vue3 学习教程,从入门到精通,使用 VSCode 开发 Vue3 的详细指南(3)
前端·javascript·vue.js·学习·前端框架·vue·vue3
姑苏洛言3 小时前
搭建一款结合传统黄历功能的日历小程序
前端·javascript·后端
时来天地皆同力.3 小时前
Java面试基础:概念
java·开发语言·jvm
hackchen3 小时前
Go与JS无缝协作:Goja引擎实战之错误处理最佳实践
开发语言·javascript·golang