【android开发】手写签名系统的设计与实现之实现pdf文件上手写签名效果(五)—完

  • Post author:
  • Post category:其他

前几篇文章我们分别介绍了显示文件列表、解析pdf、手写画板及画笔设置的功能了,今天我们就介绍一下,最后最关键的一部分-手写签名效果。先看看效果图:

                选定位置                                    画板上写字                                       预览签名效果

                     

一、实现原理:

对于pdf文件上进行相关操作,本人并没找到一些比较好的方法,为了实现签名效果,尝试了很多方法也没达到预期效果,最后这种实现方法相对好些,也比较简单。其基本思想是根据对pdf文件加印章及水印来实现的,事先我们准备一张透明的png图片,当做手写画板的背景图片,写字时实际就在这张图片操作了,最后将手写的画板内容重新保存一种新的png背景透明图片,就是对pdf文件的操作了,对pdf操作要用到第三方jar包droidText0.5.jar包,通过里面的方法Image img = Image.getInstance(InPicFilePath);完成将透明图片加入到pdf文件上,最后重新生成新的pdf文件,签名就完成了。并不是直接对pdf文件进行操作,不知道其他的实现方法有哪些,也请告知一下。下面我就把自己实现具体过程介绍一下:

新建一个类,用于处理签名pdf文件HandWriteToPDF.java:

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));
		}
	}
}

其中:

img.setAbsolutePosition(MuPDFActivity.x*5/6,pdfHigth-((MuPDFActivity.y+120)*5/6));

是用来控制要在pdf文件上的签名位置,这个得说说,我并没有搞明白怎么设置这个坐标值才是最合适的,屏幕和pdf文件的坐标原点都是在左上角,setAbsolutPosition(float x,float y)的坐标原点是在屏幕的左下角,还有就是pdf实际分辨率和手机的实际分辨率又不是相同的,在我移动坐标,通过获取的手机屏幕的坐标:

/**
	 * 功能:自定义一个显示截屏区域视图方法
	 * */
	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();
}

在类ReaderView.java中实现动态获取坐标,本人并没有搞明白屏幕坐标和pdf文件坐标已经setAbsolutPosition()方法三者之间的关系,通过很多数据测试得出了在没有放大pdf页面的情况下关系如下:

img.setAbsolutePosition(MuPDFActivity.x*5/6,pdfHigth-((MuPDFActivity.y+120)*5/6));

也就是屏幕坐标和setAbsolutPosition()坐标是5:6的关系,这样对于没有放大页面的情况下,坐标对应是非常准确的,但是当方法pdf页面再去取坐标时,就混乱了,这个问题目前还没有解决,知道好的方法的朋友一定记得告诉在下呀!

定义了三个用于保存文件路径的变量:

private String InPdfFilePath;
private String outPdfFilePath;
private String InPicFilePath;

类建好后,就是在MuPdfActivity类中对相关按钮功能进行完善了:

@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;
}

在保存文件时,对于程序来说,是比较耗时了,这样我们就用到了异步任务来完成耗时的操作:

/** 
	      * 运行在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();
    }

由于这一排操作按钮之间存在逻辑关系:比如没有确定签名位置不能打开画板等,我们就需要判断什么时候应该打开,什么打不开并提示,这样我们就定义几个布尔类型的变量:

/**
	 * 判断是否为预览pdf模式
	 */
	public static boolean isPreviewPDF = false;
	/**
	 * 判断是否正在书写
	 */
	public static boolean isWriting = false;
	/**
	 * 判断页面按钮是否显示
	 */
	private boolean showButtonsDisabled;
	/**
	 * 判断截屏视图框是否显示
	 */
	private static boolean isScreenShotViewShow = false;
/**
	 * NoTouch =false 屏蔽pdf手势操作,为true时释放pdf手势操作
	 */
	public static boolean NoTouch = true;

为了在我们进行选择位置和签名时pdf不会再监听手势操作,我们要做相应的屏蔽:

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文件上签名就完成了,采用的方法也是比较局限,目前能匹配上的只限于手机的分辨率为720*1280,其他分辨手机没有做适配。由于每次是一边写文章,一边写代码,项目会存在很多不完善的,欢迎指正。在文章的最后,我会把今天的项目代码附上下载链接地址,需要的朋友可以下载分享。
二、总结:

在做对于pdf文件的签名时,查阅了很多资料,也没有看到相对较好的实例,结合自己的想法和网上的一些资料,一步一步最终实现了签名的效果,其中存在的问题也是很大,最主要的就是坐标匹配问题,放大页面就无法正常匹配了。之前想做的效果,就是直接在pdf页面进行操作,不过没有实现成功。对于手写签名,在很多领域都用到了,目前主要以收费为主,比如一些认证中心,他们在提供签证与验签时,也提供相关的签名过程。欢迎提出更好的实现方法,大家一起进步……

2016年10月21日备注说明:

各位博友您好,很抱歉,由于本人目前已经从开发工作主要转向互联网创业,很久没有更新博客内容,一些博友的提问也未能及时回复,敬请见谅!

为保持和各位博友的互动,如遇到什么问题,可以到我们团队里面找我或者我们团队,有什么能帮助大家的,我们相互沟通:

鸿钧科技团队简介:
www.HongJunIT.com
鸿钧科技服务简介:
http://shop.zbj.com/9592213/


版权声明:本文为lixinhuixin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。