W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
編寫:jdneo - 原文:http://developer.android.com/training/printing/custom-docs.html
對于有些應(yīng)用,比如繪圖應(yīng)用,頁面布局應(yīng)用和其它一些關(guān)注于圖像輸出的應(yīng)用,創(chuàng)造出精美的打印頁面將是它的核心功能。在這種情況下,僅僅打印一幅圖片或一個HTML文檔就不夠了。這類應(yīng)用的打印輸出需要精確地控制每一個會在頁面中顯示的對象,包括字體,文本流,分頁符,頁眉,頁腳和一些圖像元素等等。
想要創(chuàng)建一個完全自定義的打印文檔,需要投入比之前討論的方法更多的編程精力。我們必須構(gòu)建可以和打印框架相互通信的組件,調(diào)整打印參數(shù),繪制頁面元素并管理多個頁面的打印。
這節(jié)課將展示如何連接打印管理器,創(chuàng)建一個打印適配器以及如何構(gòu)建出需要打印的內(nèi)容。
當(dāng)我們的應(yīng)用直接管理打印進(jìn)程時,在收到來自用戶的打印請求后,第一步要做的是連接Android打印框架并獲取一個PrintManager類的實(shí)例。該類允許我們初始化一個打印任務(wù)并開始打印任務(wù)的生命周期。下面的代碼展示了如何獲得打印管理器并開始打印進(jìn)程。
private void doPrint() {
// Get a PrintManager instance
PrintManager printManager = (PrintManager) getActivity()
.getSystemService(Context.PRINT_SERVICE);
// Set job name, which will be displayed in the print queue
String jobName = getActivity().getString(R.string.app_name) + " Document";
// Start a print job, passing in a PrintDocumentAdapter implementation
// to handle the generation of a print document
printManager.print(jobName, new MyPrintDocumentAdapter(getActivity()),
null); //
}
上面的代碼展示了如何命名一個打印任務(wù)以及如何設(shè)置一個PrintDocumentAdapter類的實(shí)例,它負(fù)責(zé)處理打印生命周期的每一步。打印適配器的實(shí)現(xiàn)會在下一節(jié)中進(jìn)行討論。
Note:print()方法的最后一個參數(shù)接收一個PrintAttributes對象。我們可以使用這個參數(shù)向打印框架進(jìn)行一些打印設(shè)置,以及基于前一個打印周期的預(yù)設(shè),從而改善用戶體驗(yàn)。我們也可以使用這個參數(shù)對打印內(nèi)容進(jìn)行一些更符合實(shí)際情況的設(shè)置,比如當(dāng)打印一幅照片時,設(shè)置打印的方向與照片方向一致。
打印適配器負(fù)責(zé)與Android打印框架交互并處理打印過程的每一步。這個過程需要用戶在創(chuàng)建打印文檔前選擇打印機(jī)和打印選項(xiàng)。由于用戶可以選擇不同性能的打印機(jī),不同的頁面尺寸或不同的頁面方向,因此這些選項(xiàng)可能會影響最終的打印效果。當(dāng)這些選項(xiàng)配置好之后,打印框架會尋求適配器進(jìn)行布局并生成一個打印文檔,以此作為打印的前期準(zhǔn)備。一旦用戶點(diǎn)擊了打印按鈕,框架會將最終的打印文檔傳遞給Print Provider進(jìn)行打印輸出。在打印過程中,用戶可以選擇取消打印,所以打印適配器必須監(jiān)聽并響應(yīng)取消打印的請求。
PrintDocumentAdapter抽象類負(fù)責(zé)處理打印的生命周期,它有四個主要的回調(diào)方法。我們必須在打印適配器中實(shí)現(xiàn)這些方法,以此來正確地和Android打印框架進(jìn)行交互:
下面將介紹如何實(shí)現(xiàn)onLayout()
以及onWrite()
方法,他們是打印適配器的核心功能。
Note:這些適配器的回調(diào)方法會在應(yīng)用的主線程上被調(diào)用。如果這些方法的實(shí)現(xiàn)在執(zhí)行時可能需要花費(fèi)大量的時間,那么應(yīng)該將他們放在另一個線程里執(zhí)行。例如:我們可以將布局或者寫入打印文檔的操作封裝在一個AsyncTask對象中。
在實(shí)現(xiàn)PrintDocumentAdapter類時,我們的應(yīng)用必須能夠指定出所創(chuàng)建文檔的類型,計(jì)算出打印任務(wù)所需要打印的總頁數(shù),并提供打印頁面的尺寸信息。在實(shí)現(xiàn)適配器的onLayout()方法時,我們執(zhí)行這些計(jì)算,并提供與理想的輸出相關(guān)的一些信息,這些信息可以在PrintDocumentInfo類中獲取,包括頁數(shù)和內(nèi)容類型。下面的例子展示了PrintDocumentAdapter中onLayout()方法的基本實(shí)現(xiàn):
@Override
public void onLayout(PrintAttributes oldAttributes,
PrintAttributes newAttributes,
CancellationSignal cancellationSignal,
LayoutResultCallback callback,
Bundle metadata) {
// Create a new PdfDocument with the requested page attributes
mPdfDocument = new PrintedPdfDocument(getActivity(), newAttributes);
// Respond to cancellation request
if (cancellationSignal.isCancelled() ) {
callback.onLayoutCancelled();
return;
}
// Compute the expected number of printed pages
int pages = computePageCount(newAttributes);
if (pages > 0) {
// Return print information to print framework
PrintDocumentInfo info = new PrintDocumentInfo
.Builder("print_output.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(pages);
.build();
// Content layout reflow is complete
callback.onLayoutFinished(info, true);
} else {
// Otherwise report an error to the print framework
callback.onLayoutFailed("Page count calculation failed.");
}
}
onLayout()方法的執(zhí)行結(jié)果有三種:完成,取消或失?。ㄓ?jì)算布局無法順利完成時會失?。?。我們必須通過調(diào)用PrintDocumentAdapter.LayoutResultCallback對象中的適當(dāng)方法來指出這些結(jié)果中的一個。
Note:onLayoutFinished()方法的布爾類型參數(shù)明確了這個布局內(nèi)容是否和上一次打印請求相比發(fā)生了改變。恰當(dāng)?shù)卦O(shè)定了這個參數(shù)將避免打印框架不必要地調(diào)用onWrite()方法,緩存之前的打印文檔,提升執(zhí)行性能。
onLayout()的主要任務(wù)是計(jì)算打印文檔的頁數(shù),并將它作為打印參數(shù)交給打印機(jī)。如何計(jì)算頁數(shù)則高度依賴于應(yīng)用是如何對打印頁面進(jìn)行布局的。下面的代碼展示了頁數(shù)是如何根據(jù)打印方向確定的:
private int computePageCount(PrintAttributes printAttributes) {
int itemsPerPage = 4; // default item count for portrait mode
MediaSize pageSize = printAttributes.getMediaSize();
if (!pageSize.isPortrait()) {
// Six items per page in landscape orientation
itemsPerPage = 6;
}
// Determine number of print items
int printItemCount = getPrintItemCount();
return (int) Math.ceil(printItemCount / itemsPerPage);
}
當(dāng)需要將打印內(nèi)容輸出到一個文件時,Android打印框架會調(diào)用PrintDocumentAdapter類的onWrite()方法。這個方法的參數(shù)指定了哪些頁面要被寫入以及要使用的輸出文件。該方法的實(shí)現(xiàn)必須將每一個請求頁的內(nèi)容渲染成一個含有多個頁面的PDF文件。當(dāng)這個過程結(jié)束以后,你需要調(diào)用callback對象的onWriteFinished()方法。
Note: Android打印框架可能會在每次調(diào)用onLayout()后,調(diào)用onWrite()方法一次甚至更多次。請務(wù)必牢記:當(dāng)打印內(nèi)容的布局沒有變化時,可以將onLayoutFinished()方法的布爾參數(shù)設(shè)置為“false”,以此避免對打印文檔進(jìn)行不必要的重寫操作。
Note:onLayoutFinished()方法的布爾類型參數(shù)明確了這個布局內(nèi)容是否和上一次打印請求相比發(fā)生了改變。恰當(dāng)?shù)卦O(shè)定了這個參數(shù)將避免打印框架不必要的調(diào)用onLayout()方法,緩存之前的打印文檔,提升執(zhí)行性能。
下面的代碼展示了使用PrintedPdfDocument類創(chuàng)建了PDF文件的基本原理:
@Override
public void onWrite(final PageRange[] pageRanges,
final ParcelFileDescriptor destination,
final CancellationSignal cancellationSignal,
final WriteResultCallback callback) {
// Iterate over each page of the document,
// check if it's in the output range.
for (int i = 0; i < totalPages; i++) {
// Check to see if this page is in the output range.
if (containsPage(pageRanges, i)) {
// If so, add it to writtenPagesArray. writtenPagesArray.size()
// is used to compute the next output page index.
writtenPagesArray.append(writtenPagesArray.size(), i);
PdfDocument.Page page = mPdfDocument.startPage(i);
// check for cancellation
if (cancellationSignal.isCancelled()) {
callback.onWriteCancelled();
mPdfDocument.close();
mPdfDocument = null;
return;
}
// Draw page content for printing
drawPage(page);
// Rendering is complete, so page can be finalized.
mPdfDocument.finishPage(page);
}
}
// Write PDF document to file
try {
mPdfDocument.writeTo(new FileOutputStream(
destination.getFileDescriptor()));
} catch (IOException e) {
callback.onWriteFailed(e.toString());
return;
} finally {
mPdfDocument.close();
mPdfDocument = null;
}
PageRange[] writtenPages = computeWrittenPages();
// Signal the print framework the document is complete
callback.onWriteFinished(writtenPages);
...
}
代碼中將PDF頁面遞交給了drawPage()方法,這個方法會在下一部分介紹。
就布局而言,onWrite()方法的執(zhí)行可以有三種結(jié)果:完成,取消或者失?。▋?nèi)容無法被寫入)。我們必須通過調(diào)用PrintDocumentAdapter.WriteResultCallback對象中的適當(dāng)方法來指明這些結(jié)果中的一個。
Note:渲染打印文檔是一個可能耗費(fèi)大量資源的操作。為了避免阻塞應(yīng)用的主UI線程,我們應(yīng)該考慮將頁面的渲染和寫操作放在另一個線程中執(zhí)行,比如在AsyncTask中執(zhí)行。關(guān)于更多異步任務(wù)線程的知識,可以閱讀:Processes and Threads。
當(dāng)我們的應(yīng)用進(jìn)行打印時,應(yīng)用必須生成一個PDF文檔并將它傳遞給Android打印框架以進(jìn)行打印。我們可以使用任何PDF生成庫來協(xié)助完成這個操作。本節(jié)將展示如何使用PrintedPdfDocument類將打印內(nèi)容生成為PDF頁面。
PrintedPdfDocument類使用一個Canvas對象來在PDF頁面上繪制元素,這一點(diǎn)和在activity布局上進(jìn)行繪制很類似。我們可以在打印頁面上使用Canvas類提供的相關(guān)繪圖方法繪制頁面元素。下面的代碼展示了如何使用這些方法在PDF頁面上繪制一些簡單的元素:
private void drawPage(PdfDocument.Page page) {
Canvas canvas = page.getCanvas();
// units are in points (1/72 of an inch)
int titleBaseLine = 72;
int leftMargin = 54;
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTextSize(36);
canvas.drawText("Test Title", leftMargin, titleBaseLine, paint);
paint.setTextSize(11);
canvas.drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint);
paint.setColor(Color.BLUE);
canvas.drawRect(100, 100, 172, 172, paint);
}
當(dāng)使用Canvas在一個PDF頁面上繪圖時,元素通過單位“點(diǎn)(point)”來指定大小,一個點(diǎn)相當(dāng)于七十二分之一英寸。在編寫程序時,請確保使用該測量單位來指定頁面上的元素大小。在定位繪制的元素時,坐標(biāo)系的原點(diǎn)(即(0,0)點(diǎn))在頁面的最左上角。
Tip:雖然Canvas對象允許我們將打印元素放置在一個PDF文檔的邊緣,但許多打印機(jī)無法在紙張的邊緣打印。所以當(dāng)我們使用這個類構(gòu)建一個打印文檔時,需要考慮到那些無法打印的邊緣區(qū)域。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: