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('签名保存失败');
          }
        }
      },
    );
  }
相关推荐
gotoc丶22 分钟前
堆排序:力扣215.数组中的第K个大元素
javascript·数据结构·算法·leetcode·排序算法
Tiger Z1 小时前
R 语言科研绘图 --- 密度图-汇总
开发语言·程序人生·r语言·贴图
叶雅茗2 小时前
PHP语言的区块链扩展性
开发语言·后端·golang
双叶8363 小时前
(C语言)写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和(递归函数)
c语言·开发语言·数据结构·算法·游戏
大龄大专大前端3 小时前
JavaScript闭包的认识/应用/原理
前端·javascript·ecmascript 6
烛阴3 小时前
JavaScript 函数绑定:从入门到精通,解锁你的代码超能力!
前端·javascript
花椒和蕊3 小时前
【vue+excel】导出excel(目前是可以导出两个sheet)
javascript·vue.js·excel
泫凝3 小时前
使用 WebP 优化 GPU 纹理占用
前端·javascript
returnShitBoy4 小时前
前端面试:React hooks 调用是可以写在 if 语句里面吗?
前端·javascript·react.js
“抚琴”的人4 小时前
C#—线程池详解
开发语言·c#