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;
}
@Override
public 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, 72dpi
pageWidth =
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.");
}
}
@Override
public 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();//创建对应的Page
PdfDocument.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 line
if (pageWidth > pageHeight){
drawViewsOnPaper(Canvas canvas, Paint paint);
}
}
//draw views
private 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