android 通過wifi呼叫印表機
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.然後說下程式碼如何實現,一般用的到也就列印圖片或者文件,先說列印圖片,程式碼如下:
裡面有部分需要解釋下,填充型別ScaleMode一共兩種,原始碼中的解釋如下: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 }
/**
* 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