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. 手势处理
支持拖动、缩放签名图像,需要用到 GestureDetector
、ScaleGestureDetector
等类处理触摸事件。
5. 文件导出与分享
签名完成后支持保存至本地或分享至其他应用(微信、邮箱等)。
三、项目实现思路
整个项目分为以下几个核心流程:
-
PDF 文件的读取与展示
-
使用 PdfRenderer 或第三方库展示 PDF 文件供用户查看;
-
-
签名画板
-
用户在画布上书写签名,保存为 Bitmap;
-
-
签名插入
-
用户可拖动签名图像定位到 PDF 指定页面与位置;
-
-
合并签名进 PDF
-
将签名 Bitmap 插入 PDF 指定页面;
-
-
导出并保存 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 文件系统与权限机制;
-
熟悉触控手势与手势识别处理。