android是可以通过wifi调用打印机打印图片或者文档的,在API19之前,调用打印机是通过Socket通信然后打印东西的,Socket是比较原始的通信模式,也是相对比较底层的,一般通过端口连接是可以连接任意两台机器进行数据传输并操作的,打印机也不例外。但是API19之后,android有了自己的打印框架,就是Kit Kat Print,这个打印框架可以直接生成pdf或者打印图片或者文档,这里的文档就是指类似于word之类的东西,直接操作View绘制文档,然后生成pdf或者通过wifi调用打印机打印。
这算是android的边缘技术,毕竟用这个的并不是很多,大致说下。
1.Kit Kat Print这个打印框架只提供打印功能,至于搜索功能也不知道是没实现,还是根本就没有。我用的方式是先下载Hp print service 插件,这是个移动端的service类型的app,提供搜索wifi下同一网段的打印机,安装后没有界面。如果没用这个插件的话,会显示一直在搜索中,安装完成后,在设置里面就会有打印机的选项了,某些机型里面是在设置里面,比如leX820,有些机型干脆就没有,比如Vivo,但是仍然能够扫描的到打印机。
Hp print service插件可以直接在各种应用商店里面找,很容易搜得到的。
2.然后说下代码如何实现,一般用的到也就打印图片或者文档,先说打印图片,代码如下:
private void doPhotoPrint() {//申请sd卡权限ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS},4);//实例化类PrintHelper photoPrinter = new PrintHelper(this);photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);//设置填充的类型,填充的类型指的是在A4纸上打印时的填充类型,两种模式//打印Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + "/doctor/testprint.jpeg");photoPrinter.printBitmap("jpgTestPrint", bitmap);//这里的第一个参数是打印的jobName
}
里面有部分需要解释下,填充类型ScaleMode一共两种,源码中的解释如下:
/*** image will be scaled but leave white space*/public static final int SCALE_MODE_FIT = 1;/*** image will fill the paper and be cropped (default)*/public static final int SCALE_MODE_FILL = 2;
仍然解释的不是很明朗,解释下:Fit模式下,你所打印的图片会缩放至A4纸能够显示所有的图片内容;Fill模式下,你所打印的图片会把打印的图片拉伸直到某条边能够和对应的A4纸的边长度一致。
另外要注意的是printBitmap的第一个参数,如果打印2次以上的时候,每次传进去的参数最好不一致,这样不会打印错。
2.比较麻烦的是打印文档,打印文档的话就比较适合平常用电脑操作打印文档了,就类似于在自定义View上面画东西一样,先给出打印的方法:
//print picture in document
private void onPrintDoc(String jobName, String absPicturePath){PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);PrintAttributes.Builder builder = new PrintAttributes.Builder();builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR);//设置色彩模式,黑白或者彩色,不管用builder.setMinMargins(new PrintAttributes.Margins(300, 200, 300, 200));//设置页边距,就是打印的有效区域距离纸的边缘部分的距离,不管用
// builder.setDuplexMode(DUPLEX_MODE_SHORT_EDGE);//设置按照横着打印还是竖着打印,不管用PrintAttributes.MediaSize temp = PrintAttributes.MediaSize.ISO_A4;//设置打印的纸张的类型,比如A4,A8等,不管用temp.asLandscape();Log.i("blb", "--------is portrait:" + temp.isPortrait());builder.setMediaSize(temp);//这个也不管用
// builder.setResolution(new PrintAttributes.Resolution("white", "whiteRadish", 150, 150));//设置打印纸的分辨率,这个也不管用MyPrintAdapter myPrintAdapter = new MyPrintAdapter(this, absPicturePath);printManager.print(jobName, myPrintAdapter, builder.build());
}
看到写了那么多参数其实一个都不管用,因为hp print service 并不接受这些参数设定,所以设定啥都不行。另外打印机的分辨率是可以设定的,比如300dpi,600dpi,如果不明白可以参考我之前写的像素的转换文章。
前面的是调用的方式,导一下包就行了,没有比较麻烦的,涉及到一个MyPrintAdapter这个需要自己写,这里需要大概说下,在android里面,只要是adapter的名字的类,一般都是处理数据,或者回掉,或者各种中间过程转换的,这个MyPrintAdapter也类似,先看下示例代码:
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.pdf.PdfDocument;
import android.graphics.pdf.PdfDocument.PageInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.pdf.PrintedPdfDocument;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Log;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** Created by whiteRadish on 2017/6/14.*/@TargetApi(Build.VERSION_CODES.KITKAT)
public class MyPrintAdapter extends PrintDocumentAdapter {Context mContext;private int pageHeight;private int pageWidth;public PdfDocument myPdfDocument;public int totalpages = 1;//设置一共打印一张纸public MyPrintAdapter(Context context) {//这里传各种需要的参数就行this.mContext = context;}@Overridepublic void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal,LayoutResultCallback callback,Bundle metadata) {Log.i("blb", "--------run onLayout");myPdfDocument = new PrintedPdfDocument(mContext, newAttributes); //创建可打印PDF文档对象pageHeight =newAttributes.getMediaSize().getHeightMils() / 1000 * 72; //设置尺寸,为什么是1000 * 72, 72dpipageWidth =newAttributes.getMediaSize().getWidthMils() / 1000 * 72;if (totalpages > 0) {PrintDocumentInfo.Builder builder = new PrintDocumentInfo.Builder("whiteRadish").setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(totalpages); //构建文档配置信息PrintDocumentInfo info = builder.build();callback.onLayoutFinished(info, true);} else {callback.onLayoutFailed("Page count is zero.");}}@Overridepublic void onWrite(final PageRange[] pageRanges, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal,final WriteResultCallback callback) {Log.i("blb", "--------run onWrite");for (int i = 0; i < totalpages; i++) {if (pageInRange(pageRanges, i)) //保证页码正确{PageInfo newPage = new PageInfo.Builder(pageWidth,pageHeight, i).create();//创建对应的PagePdfDocument.Page page =myPdfDocument.startPage(newPage); //创建新页面if (cancellationSignal.isCanceled()) { //取消信号callback.onWriteCancelled();myPdfDocument.close();myPdfDocument = null;return;}drawPage(page, i); //将内容绘制到页面Canvas上myPdfDocument.finishPage(page);}}try {myPdfDocument.writeTo(new FileOutputStream(destination.getFileDescriptor()));} catch (IOException e) {callback.onWriteFailed(e.toString());return;} finally {myPdfDocument.close();myPdfDocument = null;}callback.onWriteFinished(pageRanges);}private boolean pageInRange(PageRange[] pageRanges, int page) {for (int i = 0; i < pageRanges.length; i++) {if ((page >= pageRanges[i].getStart()) &&(page <= pageRanges[i].getEnd()))return true;}return false;}//页面绘制private void drawPage(PdfDocument.Page page,int pagenumber) {Canvas canvas = page.getCanvas();//这里是页码。页码不能从0开始pagenumber++;Paint paint = new Paint();PageInfo pageInfo = page.getInfo();//draw heart rate lineif (pageWidth > pageHeight){drawViewsOnPaper(Canvas canvas, Paint paint);}}//draw viewsprivate void drawViewsOnPaper(Canvas canvas, Paint paint){//这里绘制要在打印的纸上的内容,如果精确一点的话,根据pageHeight,pageWidth计算就行,这里的内容和自定义View的内容一样,//把自定义View绘制的东西拉过来直接就可以用}
}
大概解释下,这个MyPrintAdapter继承自PrintDocumentAdapter,PrintDocumentAdapter有自己的生命周期,这个生命周期是和打印时的操作对应上的,如果有需要的参数变量可以通过构造方法传进去,比如我就传了个Context,虽然没怎么用,他的生命周期依次为onStart,onLayout,onWrite,onFinish,从名字上就能看出来大概什么时候调用,start用在刚开始的时候,layout的回调是在生成打印预览的时候布局时候的回调,代码在写的过程就需要把所需要的东西绘制上去,这时就走了write的回调,在write的回调里面用自定义view的方式绘制需要的内容就行了,finish就是完事了。这个生命周期比较简单,可以自行参照源码看看就知道大致流程了。WriteResultCallBack这个回调就是绘制东西的流程,也有自己的周期,可以通过回调的方式通知PrintDocumentAdapter绘制的相关结果失败或者成功。其余部分代码上都有解释。
这里面有个比较麻烦的地方就是:android在绘制的时候是按照dpi,或者px来绘制内容的,但是在纸上的话就是按照cm来绘制内容的,这中间有一个比例转化关系,getHeightMiles获取到mile值,这个值是比英寸还小的单位,转化为英寸后,比如转化为英寸后*72就是72dots per inch,就是72dpi,当然你可以设置的更高,根据打印机的配置试试看吧。
另外需要注意的是,用手机打印比不上电脑,没有想象的那么快,需要等一小会儿。
3.参考的内容,可以自己查看sdk下的source源文件,sdk源文件查看方式,比如类的包名是package android.print,直接去source下android文件夹下的print文件夹就能找到。或者官方API,官方API链接:http://www.android-doc.com/reference/android/print/PrintDocumentAdapter.html