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