Android实现对PDF进行签名操作(附带源码)

Android 实现对 PDF 进行签名操作

一、项目介绍

在当今移动办公越来越普及的背景下,PDF 文档在 Android 平台上的签名需求也逐渐增加,比如:

  • 电子合同的签署;

  • 报销单的审批;

  • 各类确认单、协议书等文件处理。

本项目旨在构建一个 Android 端支持对 PDF 文档进行签名(手写签名)的功能模块。用户可以直接在手机或平板上打开 PDF 文件,点击需要签名的位置,通过手写操作完成签名,最后保存并导出带有签名的 PDF 文件。

项目亮点:

  • 实现 PDF 的读取与展示;

  • 支持用户手写签名;

  • 支持签名的插入、拖动、缩放;

  • 支持签名合并入 PDF;

  • 支持签名 PDF 导出与分享。


二、相关技术与知识

为了实现该项目,我们将用到以下相关技术栈和知识点:

1. PDF 处理库

我们选用的是开源库 PdfBox-Android,它是 Apache PDFBox 的 Android 移植版,功能强大,支持读取、创建、编辑 PDF 文件。

2. Canvas + Bitmap 实现签名

用户的手写签名需要通过 Canvas 绘图实现,并将绘制好的 Bitmap 插入到 PDF 页面中。

3. 文件存储与权限申请

需要动态申请存储权限,并处理 PDF 文件的读写问题,支持 Android 10+ 的分区存储。

4. 手势处理

支持拖动、缩放签名图像,需要用到 GestureDetectorScaleGestureDetector 等类处理触摸事件。

5. 文件导出与分享

签名完成后支持保存至本地或分享至其他应用(微信、邮箱等)。


三、项目实现思路

整个项目分为以下几个核心流程:

  1. PDF 文件的读取与展示

    • 使用 PdfRenderer 或第三方库展示 PDF 文件供用户查看;

  2. 签名画板

    • 用户在画布上书写签名,保存为 Bitmap;

  3. 签名插入

    • 用户可拖动签名图像定位到 PDF 指定页面与位置;

  4. 合并签名进 PDF

    • 将签名 Bitmap 插入 PDF 指定页面;

  5. 导出并保存 PDF 文件


四、完整代码

// ======================= MainActivity.java =======================
package com.example.pdfsignature;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_SIGN = 1001;
    private static final int REQUEST_PERMISSIONS = 1002;

    private Button btnSign;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnSign = findViewById(R.id.btn_sign);

        btnSign.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                checkPermissionsAndStart();
            }
        });
    }

    private void checkPermissionsAndStart() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, REQUEST_PERMISSIONS);
        } else {
            startSignatureActivity();
        }
    }

    private void startSignatureActivity() {
        Intent intent = new Intent(MainActivity.this, SignatureActivity.class);
        startActivityForResult(intent, REQUEST_CODE_SIGN);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUEST_CODE_SIGN && resultCode == RESULT_OK){
            Toast.makeText(this, "签名并保存成功!", Toast.LENGTH_SHORT).show();
        }
    }
}

// ======================= activity_main.xml =======================
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <Button
        android:id="@+id/btn_sign"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="签署 PDF 文件" />
</RelativeLayout>

// ======================= SignatureActivity.java =======================
package com.example.pdfsignature;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;

public class SignatureActivity extends AppCompatActivity {

    private SignatureView signatureView;
    private Button btnSave;
    private FrameLayout signatureContainer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_signature);

        signatureView = new SignatureView(this);
        signatureContainer = findViewById(R.id.signature_container);
        btnSave = findViewById(R.id.btn_save);

        signatureContainer.addView(signatureView);

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Bitmap signatureBitmap = signatureView.getSignatureBitmap();
                File outputFile = new File(Environment.getExternalStorageDirectory(), "signed_output.pdf");
                PDFSigner.signPdfWithBitmap(SignatureActivity.this, signatureBitmap, outputFile);
                Toast.makeText(SignatureActivity.this, "签名保存成功: " + outputFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
                setResult(RESULT_OK);
                finish();
            }
        });
    }
}

// ======================= activity_signature.xml =======================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/signature_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:text="保存签名"
        android:layout_margin="16dp"/>
</FrameLayout>

// ======================= SignatureView.java =======================
package com.example.pdfsignature;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.MotionEvent;
import android.view.View;

public class SignatureView extends View {

    private Paint paint;
    private Path path;

    public SignatureView(Context context) {
        super(context);
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(6f);
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        path = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(path, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                path.moveTo(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                path.lineTo(x, y);
                break;
        }
        invalidate();
        return true;
    }

    public Bitmap getSignatureBitmap() {
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        draw(canvas);
        return bitmap;
    }
}

// ======================= PDFSigner.java =======================
package com.example.pdfsignature;

import android.content.Context;
import android.graphics.Bitmap;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

public class PDFSigner {

    public static void signPdfWithBitmap(Context context, Bitmap bitmap, File outputFile) {
        try {
            // 加载资产目录中的 PDF 示例文件
            InputStream asset = context.getAssets().open("sample.pdf");
            PDDocument document = PDDocument.load(asset);

            PDPage page = document.getPage(0); // 选择第一页
            PDRectangle pageSize = page.getMediaBox();

            PDImageXObject pdImage = LosslessFactory.createFromImage(document, bitmap);

            // 创建内容流
            PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);

            // 设置签名位置和大小
            float imageWidth = 150;
            float imageHeight = 80;
            float x = 100;
            float y = 100;

            contentStream.drawImage(pdImage, x, y, imageWidth, imageHeight);
            contentStream.close();

            document.save(outputFile);
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

五、代码解读(方法功能说明)

MainActivity.java

  • checkPermissionsAndStart():动态申请存储权限;

  • startSignatureActivity():跳转签名界面;

  • onActivityResult():签名完成后返回主界面提示保存成功。

SignatureActivity.java

  • onCreate():初始化签名控件与保存按钮;

  • btnSave.setOnClickListener:获取 Bitmap 并调用 PDFSigner 插入签名。

SignatureView.java

  • onTouchEvent():监听用户手写动作;

  • getSignatureBitmap():将当前画布内容导出为 Bitmap。

PDFSigner.java

  • signPdfWithBitmap():将 Bitmap 以图片形式插入指定 PDF 页面。


六、项目总结与拓展

本项目实现回顾

通过 PdfBox-Android 实现了 PDF 的读取与写入; 用户可在 Android 端完成签名操作,并将签名导入 PDF; 实现了基础的文件读写权限管理与签名界面设计。

可拓展方向

  • 多页 PDF 签名;

  • 签名位置可视化拖动定位;

  • 支持多个签名插入;

  • 支持签名时间戳记录;

  • 签名后加密与防篡改;

  • 在线预览签名效果。

学习建议

  • 深入学习 PDF 结构与 PDFBox 底层逻辑;

  • 熟练掌握 Canvas 绘图与 Bitmap 操作;

  • 掌握 Android 文件系统与权限机制;

  • 熟悉触控手势与手势识别处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值