前几篇文章我们分别介绍了显示文件列表、解析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));
- /**
- * 功能:自定义一个显示截屏区域视图方法
- * */
- 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();
- }
- img.setAbsolutePosition(MuPDFActivity.x*5/6,pdfHigth-((MuPDFActivity.y+120)*5/6));
定义了三个用于保存文件路径的变量:
- private String InPdfFilePath;
- private String outPdfFilePath;
- private String InPicFilePath;
- @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文件的签名时,查阅了很多资料,也没有看到相对较好的实例,结合自己的想法和网上的一些资料,一步一步最终实现了签名的效果,其中存在的问题也是很大,最主要的就是坐标匹配问题,放大页面就无法正常匹配了。之前想做的效果,就是直接在pdf页面进行操作,不过没有实现成功。对于手写签名,在很多领域都用到了,目前主要以收费为主,比如一些认证中心,他们在提供签证与验签时,也提供相关的签名过程。欢迎提出更好的实现方法,大家一起进步……