前几篇文章我们分别介绍了显示文件列表、解析pdf、手写画板及画笔设置的功能了,今天我们就介绍一下,最后最关键的一部分-手写签名效果。先看看效果图:
选定位置 画板上写字 预览签名效果
一、实现原理:
对于pdf文件上进行相关操作,本人并没找到一些比较好的方法,为了实现签名效果,尝试了很多方法也没达到预期效果,最后这种实现方法相对好些,也比较简单。其基本思想是根据对pdf文件加印章及水印来实现的,事先我们准备一张透明的png图片,当做手写画板的背景图片,写字时实际就在这张图片操作了,最后将手写的画板内容重新保存一种新的png背景透明图片,就是对pdf文件的操作了,对pdf操作要用到第三方jar包droidText0.5.jar包,通过里面的方法Image img = Image.getInstance(InPicFilePath);完成将透明图片加入到pdf文件上,最后重新生成新的pdf文件,签名就完成了。并不是直接对pdf文件进行操作,不知道其他的实现方法有哪些,也请告知一下。下面我就把自己实现具体过程介绍一下:
新建一个类,用于处理签名pdf文件HandWriteToPDF.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
package
com.xinhui.handwrite;
import
java.io.FileOutputStream;
import
java.io.InputStream;
import
java.security.PrivateKey;
import
java.security.PublicKey;
import
android.util.Log;
import
com.artifex.mupdf.MuPDFActivity;
import
com.artifex.mupdf.MuPDFPageView;
import
com.lowagie.text.Image;
import
com.lowagie.text.pdf.PdfArray;
import
com.lowagie.text.pdf.PdfContentByte;
import
com.lowagie.text.pdf.PdfDictionary;
import
com.lowagie.text.pdf.PdfName;
import
com.lowagie.text.pdf.PdfObject;
import
com.lowagie.text.pdf.PdfReader;
import
com.lowagie.text.pdf.PdfStamper;
import
com.xinhui.electronicsignature.R;
public
class
HandWriteToPDF {
private
String InPdfFilePath;
private
String outPdfFilePath;
private
String InPicFilePath;
public
static
int
writePageNumble;
//要签名的页码
HandWriteToPDF(){
}
public
HandWriteToPDF(String InPdfFilePath,String outPdfFilePath,String InPicFilePath){
this
.InPdfFilePath = InPdfFilePath;
this
.outPdfFilePath = outPdfFilePath;
this
.InPicFilePath = InPicFilePath;
}
public
String getInPdfFilePath() {
return
InPdfFilePath;
}
public
void
setInPdfFilePath(String inPdfFilePath) {
InPdfFilePath = inPdfFilePath;
}
public
String getOutPdfFilePath() {
return
outPdfFilePath;
}
public
void
setOutPdfFilePath(String outPdfFilePath) {
this
.outPdfFilePath = outPdfFilePath;
}
public
String getInPicFilePath() {
return
InPicFilePath;
}
public
void
setInPicFilePath(String inPicFilePath) {
InPicFilePath = inPicFilePath;
}
public
void
addText(){
try
{
PdfReader reader =
new
PdfReader(InPdfFilePath,
"PDF"
.getBytes());
//选择需要印章的pdf
FileOutputStream outStream =
new
FileOutputStream(outPdfFilePath);
PdfStamper stamp;
stamp =
new
PdfStamper(reader, outStream);
//加完印章后的pdf
PdfContentByte over = stamp.getOverContent(writePageNumble);
//设置在第几页打印印章
//用pdfreader获得当前页字典对象.包含了该页的一些数据.比如该页的坐标轴信
PdfDictionary p = reader.getPageN(writePageNumble);
//拿到mediaBox 里面放着该页pdf的大小信息.
PdfObject po = p.get(
new
PdfName(
"MediaBox"
));
//po是一个数组对象.里面包含了该页pdf的坐标轴范围.
PdfArray pa = (PdfArray) po;
Image img = Image.getInstance(InPicFilePath);
//选择图片
img.setAlignment(
1
);
img.scaleAbsolute(
300
,
150
);
//控制图片大小,原始比例720:360
//调用书写pdf位置方法
writingPosition(img ,pa.getAsNumber(pa.size()-
1
).floatValue());
over.addImage(img);
stamp.close();
}
catch
(Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 功能:处理要书写pdf位置
* @param img
*/
private
void
writingPosition(Image img ,
float
pdfHigth){
int
pdfSizeX = MuPDFPageView.pdfSizeX;
int
pdfSizeY = MuPDFPageView.pdfSizeY;
int
pdfPatchX = MuPDFPageView.pdfPatchX;
int
pdfPatchY = MuPDFPageView.pdfPatchY;
int
pdfPatchWidth = MuPDFPageView.pdfPatchWidth;
int
pdfPatchHeight = MuPDFPageView.pdfPatchHeight;
int
y = MuPDFActivity.y+
180
;
float
n = pdfPatchWidth*
1
.0f;
float
m = pdfPatchHeight*
1
.0f;
n = pdfSizeX/n;
m = pdfSizeY/m;
if
(n ==
1
.0f){
//pdf页面没有放大时的比例
if
(MuPDFActivity.y >=
900
){
img.setAbsolutePosition(MuPDFActivity.x*
5
/
6
,
0
);
}
else
if
(MuPDFActivity.y <=
60
){
img.setAbsolutePosition(MuPDFActivity.x*
5
/
6
,pdfHigth-
150
);
}
else
{
img.setAbsolutePosition(MuPDFActivity.x*
5
/
6
,pdfHigth-((MuPDFActivity.y+
120
)*
5
/
6
));
}
}
else
{
//pdf页面放大时的比例,这里坐标不精确???
n = (MuPDFActivity.x+pdfPatchX)/n;
m = (MuPDFActivity.y+pdfPatchY)/m;
img.setAbsolutePosition(n*
5
/
6
,pdfHigth-((m+
120
)*
5
/
6
));
}
}
}
|
其中:
1
|
img.setAbsolutePosition(MuPDFActivity.x*
5
/
6
,pdfHigth-((MuPDFActivity.y+
120
)*
5
/
6
));
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
/**
* 功能:自定义一个显示截屏区域视图方法
* */
public
void
screenShot(MotionEvent e2){
//这里实现截屏区域控制
/*if(MuPDFActivity.screenShotView == null || !(MuPDFActivity.screenShotView.isShown())){
MuPDFActivity.screenShotView = new MyView(MuPDFActivity.THIS);
MuPDFActivity.THIS.addContentView(MuPDFActivity.screenShotView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}*/
MuPDFActivity.oX = (
int
) e2.getX();
MuPDFActivity.oY = (
int
) e2.getY();
View screenView =
new
View(MuPDFActivity.THIS);
screenView = MuPDFActivity.THIS.getWindow().getDecorView();
screenView.setDrawingCacheEnabled(
true
);
screenView.buildDrawingCache();
Bitmap screenbitmap = screenView.getDrawingCache();
screenWidth = screenbitmap.getWidth();
screenHeight = screenbitmap.getHeight();
int
oX = MuPDFActivity.oX;
int
oY = MuPDFActivity.oY;
int
x =
0
;
int
y =
0
;
int
m =
0
;
int
n =
0
;
//oX = (int) event.getX();
//oY = (int) event.getY();
if
(oX -
180
<=
0
){
if
(oY -
90
<=
0
){
//左边界和上边界同时出界
x =
0
;
y =
0
;
m =
360
;
n =
180
;
}
else
if
(oY +
90
>= screenHeight){
//左边界和下边界同时出界
x =
0
;
y = screenHeight -
180
;
m =
360
;
n = screenHeight;
}
else
{
//只有左边界
x =
0
;
y = oY -
90
;
m =
360
;
n = y +
180
;
}
}
else
if
(oX +
180
>= screenWidth){
if
(oY -
90
<=
0
){
//右边界和上边界同时出界
x = screenWidth -
360
;
y =
0
;
m = screenWidth;
n = y +
180
;
}
else
if
(oY +
90
>= screenHeight){
//右边界和下边界同时出界
}
else
{
//只有右边界出界
x = screenWidth -
360
;
y = oY -
90
;
m = screenWidth;
n = y +
180
;
}
}
else
if
(oY -
90
<=
0
){
//只有上边界出界
x = oX -
90
;
y =
0
;
m = x +
360
;
n = y +
180
;
}
else
if
(oY +
90
>= screenHeight){
//只有下边界出界
x = oX -
180
;
y = screenHeight -
180
;
m = x +
360
;
n = y +
180
;
}
else
{
//都不出界
x = oX -
180
;
y = oY -
90
;
m = x +
360
;
n = y +
180
;
}
//根据屏幕坐标,显示要截图的区域范围
MuPDFActivity.x = x;
MuPDFActivity.y = y;
MuPDFActivity.screenShotView.setSeat(x, y, m, n);
MuPDFActivity.screenShotView.postInvalidate();
}
|
1
|
img.setAbsolutePosition(MuPDFActivity.x*
5
/
6
,pdfHigth-((MuPDFActivity.y+
120
)*
5
/
6
));
|
定义了三个用于保存文件路径的变量:
1
2
3
|
private
String InPdfFilePath;
private
String outPdfFilePath;
private
String InPicFilePath;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
@Override
public
void
onClick(View v) {
// TODO Auto-generated method stub
switch
(v.getId()) {
case
R.id.cancel_bt:
//撤销已签名pdf文件
if
(isPreviewPDF){
AlertDialog.Builder builder =
new
Builder(
this
);
builder.setTitle(
"提醒:撤销后,已签名文件文件将无法恢复,是否继续?"
)
.setPositiveButton(
"继续"
,
new
DialogInterface.OnClickListener() {
@Override
public
void
onClick(DialogInterface arg0,
int
arg1) {
// TODO Auto-generated method stub
try
{
core = openFile(InPdfFilePath);
File file =
new
File(OutPdfFilePath);
file.delete();
}
catch
(Exception e) {
// TODO: handle exception
Toast.makeText(MuPDFActivity.
this
,
"无法打开该文件"
, Toast.LENGTH_SHORT).show();
}
createUI(InstanceState);
isPreviewPDF =
false
;
//重新解析pdf,恢复初始值
ReaderView.NoTouch =
true
;
//重新释放对pdf手势操作
isScreenShotViewShow =
false
;
//重新解析pdf,恢复初始值
isWriting =
false
;
//
showButtonsDisabled =
false
;
}
})
.setNegativeButton(
"取消"
,
null
)
.create()
.show();
}
else
{
Toast.makeText(
this
,
"没有要撤销的签名文件"
, Toast.LENGTH_SHORT).show();
}
break
;
case
R.id.clear_bt:
//清除画板字迹
if
(mAddPicButton.getContentDescription().equals(
"取消签名"
)){
handWritingView.clear();
}
else
{
Toast.makeText(
this
,
"手写板未打开"
, Toast.LENGTH_SHORT).show();
}
break
;
case
R.id.add_pic_bt:
//打开手写画板
//记录当前签名页码
writingPageNumble = mDocView.getDisplayedViewIndex();
if
(mAddPicButton.getContentDescription().equals(
"开始签名"
)){
if
(screenShotView.isShown()){
screenShotView.setVisibility(View.INVISIBLE);
handWritingView.setVisibility(View.VISIBLE);
mAddPicButton.setContentDescription(
"取消签名"
);
mScreenShot.setContentDescription(
"锁定屏幕"
);
isWriting =
true
;
}
else
if
(isPreviewPDF){
Toast.makeText(MuPDFActivity.
this
,
"预览模式"
, Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(MuPDFActivity.
this
,
"请先选定书写区域"
, Toast.LENGTH_SHORT).show();
}
}
else
{
handWritingView.setVisibility(View.GONE);
mAddPicButton.setContentDescription(
"开始签名"
);
isWriting =
false
;
ReaderView.NoTouch =
true
;
//释放pdf手势操作
}
break
;
case
R.id.screenshot_ib:
//打开区域选择view
if
(screenShotView ==
null
){
screenShotView =
new
ScreenShotView(
this
);
}
if
(isPreviewPDF){
Toast.makeText(MuPDFActivity.
this
,
"预览模式"
, Toast.LENGTH_SHORT).show();
}
else
if
(!isPreviewPDF && isWriting){
Toast.makeText(MuPDFActivity.
this
,
"正在签名……"
, Toast.LENGTH_SHORT).show();
}
else
{
if
(!screenShotView.isShown() && !isScreenShotViewShow){
this
.addContentView(screenShotView,
new
LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
screenShotView.setSeat(x, y, x+
360
, y+
180
);
screenShotView.postInvalidate();
isScreenShotViewShow =
true
;
}
if
(mScreenShot.getContentDescription().equals(
"锁定屏幕"
)){
ReaderView.NoTouch =
false
;
mScreenShot.setContentDescription(
"释放屏幕"
);
screenShotView.setVisibility(View.VISIBLE);
}
else
{
ReaderView.NoTouch =
true
;
mScreenShot.setContentDescription(
"锁定屏幕"
);
screenShotView.setVisibility(View.INVISIBLE);
}
}
break
;
case
R.id.confirm_bt:
//保存签名文件
if
(mAddPicButton.getContentDescription().equals(
"取消签名"
)){
saveImageAsyncTask asyncTask =
new
saveImageAsyncTask(
this
);
asyncTask.execute();
ReaderView.NoTouch =
true
;
handWritingView.setVisibility(View.INVISIBLE);
mAddPicButton.setContentDescription(
"开始签名"
);
isPreviewPDF =
true
;
showButtonsDisabled =
false
;
}
else
{
Toast.makeText(
this
,
"没有要保存的签名文件"
, Toast.LENGTH_SHORT).show();
}
break
;
default
:
break
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
/**
* 运行在UI线程中,在调用doInBackground()之前执行
*/
@Override
protected
void
onPreExecute() {
// TODO Auto-generated method stub
Toast.makeText(context,
"正在处理……"
,Toast.LENGTH_SHORT).show();
}
/**
*后台运行的方法,可以运行非UI线程,可以执行耗时的方法
*/
@Override
protected
Integer doInBackground(Void... arg0) {
// TODO Auto-generated method stub
mSaveImage();
return
null
;
}
/**
* 运行在ui线程中,在doInBackground()执行完毕后执行
*/
@Override
protected
void
onPostExecute(Integer result) {
// TODO Auto-generated method stub
//super.onPostExecute(result);
createUI(InstanceState);
Toast.makeText(context,
"签名完成"
,Toast.LENGTH_SHORT).show();
}
/**
* 在publishProgress()被调用以后执行,publishProgress()用于更新进度
*/
@Override
protected
void
onProgressUpdate(Integer... values) {
// TODO Auto-generated method stub
super
.onProgressUpdate(values);
}
}
/**
* 功能:处理书写完毕的画板,重新生成bitmap
*/
public
void
mSaveImage(){
HandWritingView.saveImage = Bitmap.createBitmap(handWritingView.HandWriting(HandWritingView.new1Bitmap));
HandWritingView mv = handWritingView;
storeInSDBitmap = mv.saveImage();
Canvas canvas =
new
Canvas(storeInSDBitmap);
Paint paint =
new
Paint();
canvas.drawARGB(
0
,
0
,
0
,
0
);
canvas.isOpaque();
paint.setAlpha(
255
);
//设置签名水印透明度
//这个方法 第一个参数是图片原来的大小,第二个参数是 绘画该图片需显示多少。
//也就是说你想绘画该图片的某一些地方,而不是全部图片,
//第三个参数表示该图片绘画的位置.
canvas.drawBitmap(storeInSDBitmap,
0
,
0
, paint);
storeInSD(storeInSDBitmap);
//保存签名过的pdf文件
previewPDFShow();
}
/**
* 功能:预览签名过的pdf
*/
public
void
previewPDFShow(){
String openNewPath = OutPdfFilePath;
try
{
core = openFile(openNewPath);
//打开已经签名好的文件进行预览
//截屏坐标恢复默认
x =
200
;
y =
200
;
}
catch
(Exception e) {
// TODO: handle exception
Log.e(
"info"
,
"------打开失败"
);
}
}
/**
* 功能:将签好名的bitmap保存到sd卡
* @param bitmap
*/
public
static
void
storeInSD(Bitmap bitmap) {
File file =
new
File(
"/sdcard/签名"
);
//要保存的文件地址和文件名
if
(!file.exists()) {
file.mkdir();
}
File imageFile =
new
File(file,
"签名"
+
".png"
);
try
{
imageFile.createNewFile();
FileOutputStream fos =
new
FileOutputStream(imageFile);
bitmap.compress(Bitmap.CompressFormat.PNG,
100
, fos);
fos.flush();
fos.close();
addTextToPdf();
}
catch
(FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public
static
void
addTextToPdf(){
String SDCardRoot = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;
SimpleDateFormat formatter =
new
SimpleDateFormat(
"yyyy-MM-dd-HH-mm-ss"
);
Date curDate =
new
Date(System.currentTimeMillis());
//获取当前时间
String currentSystemTime = formatter.format(curDate);
InPdfFilePath = MuPDFActivity.PATH;
OutPdfFilePath = SDCardRoot+
"/签名/已签名文件"
+currentSystemTime+
".pdf"
;
InPicFilePath = SDCardRoot+
"/签名/签名.png"
;
HandWriteToPDF handWriteToPDF =
new
HandWriteToPDF(InPdfFilePath, OutPdfFilePath, InPicFilePath);
handWriteToPDF.addText();
}
|
由于这一排操作按钮 之间存在逻辑关系:比如没有确定签名位置不能打开画板等,我们就需要判断什么时候应该打开,什么打不开并提示,这样我们就定义几个布尔类型的变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* 判断是否为预览pdf模式
*/
public
static
boolean
isPreviewPDF =
false
;
/**
* 判断是否正在书写
*/
public
static
boolean
isWriting =
false
;
/**
* 判断页面按钮是否显示
*/
private
boolean
showButtonsDisabled;
/**
* 判断截屏视图框是否显示
*/
private
static
boolean
isScreenShotViewShow =
false
;
|
1
2
3
4
|
/**
* NoTouch =false 屏蔽pdf手势操作,为true时释放pdf手势操作
*/
public
static
boolean
NoTouch =
true
;
|
为了在我们进行选择位置和签名时pdf不会再监听手势操作,我们要做相应的屏蔽:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
boolean
onScale(ScaleGestureDetector detector) {
//截屏视图不显示时,手势操作可以进行
if
(NoTouch){
float
previousScale = mScale;
mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE);
scalingFactor = mScale/previousScale;
//缩放比例
//Log.e("info", "--->scalingFactor="+scalingFactor);
View v = mChildViews.get(mCurrent);
if
(v !=
null
) {
// Work out the focus point relative to the view top left
int
viewFocusX = (
int
)detector.getFocusX() - (v.getLeft() + mXScroll);
int
viewFocusY = (
int
)detector.getFocusY() - (v.getTop() + mYScroll);
// Scroll to maintain the focus point
mXScroll += viewFocusX - viewFocusX * scalingFactor;
mYScroll += viewFocusY - viewFocusY * scalingFactor;
requestLayout();
}
}
return
true
;
}
|
二、总结:
在做对于pdf文件的签名时,查阅了很多资料,也没有看到相对较好的实例,结合自己的想法和网上的一些资料,一步一步最终实现了签名的效果,其中存在的问题也是很大,最主要的就是坐标匹配问题,放大页面就无法正常匹配了。之前想做的效果,就是直接在pdf页面进行操作,不过没有实现成功。对于手写签名,在很多领域都用到了,目前主要以收费为主,比如一些认证中心,他们在提供签证与验签时,也提供相关的签名过程。欢迎提出更好的实现方法,大家一起进步……