部分應用程式 (例如繪圖應用程式、頁面版面配置應用程式和主要聚焦應用程式) 產生精美的列印頁面是一大特色在本例中 列印圖片或 HTML 文件如要輸出這類應用程式的列印輸出內容, 精確控制頁面中的所有項目,包括字型、文字流動、分頁符號 包括頁首、頁尾和圖形元素
建立完全自訂的列印輸出內容為應用程式 而不是先前討論過的做法您必須建構 與列印架構通訊、根據印表機設定進行調整、繪製頁面元素 管理多個頁面的列印功能
本課程將說明如何與列印管理員連線、建立列印轉接器 建構內容以供列印
連線至列印管理員
當應用程式直接管理列印程序時,收到
使用者的列印要求是連線至 Android 列印架構並取得執行個體
PrintManager 類別的明確結構。這個類別可讓您初始化列印工作
並開始列印生命週期以下程式碼範例顯示如何取得列印管理員
再開始列印程序。
Kotlin
private fun doPrint() { activity?.also { context -> // Get a PrintManager instance val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager // Set job name, which will be displayed in the print queue val jobName = "${context.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, MyPrintDocumentAdapter(context), null) } }
Java
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); // }
上述程式碼範例示範如何為列印工作命名,並設定 PrintDocumentAdapter 類別的執行個體,以處理列印生命週期的步驟。
我們將在下一節討論列印轉接器類別的實作。
注意:print() 中的最後一個參數
方法接受 PrintAttributes 物件您可以使用這個參數
根據之前的列印週期,提供列印架構提示與預設選項。
進而改善使用者體驗您也可以使用這個參數
較適合列印內容,例如將螢幕方向設為橫向
所以在列印這種螢幕方向的相片時
建立列印轉接器
列印轉接器會與 Android 列印架構互動,並處理 這項程序會要求使用者先選取印表機和列印選項,再建立文件 供列印文件使用這些選項可能會影響使用者選擇的最終輸出結果 多個印表機的輸出功能、頁面大小或頁面方向皆不同。 設定完畢後,列印架構會要求轉接器進行配置,並產生 並準備最終輸出內容使用者輕觸列印按鈕後,架構 擷取最終的紙本文件,並傳送給列印服務供應商進行輸出。列印期間 處理程序時,使用者可以選擇取消列印動作,因此您的列印轉接器也必須監聽 回應取消要求
PrintDocumentAdapter 抽象類別的設計宗旨是處理
列印生命週期,此階段有四種主要回呼方法。您必須實作這些方法
才能與列印架構適當互動:
onStart()- 在此位置呼叫一次 就在列印程序開始之前如果應用程式有任何一次性準備工作 例如取得要列印資料的快照,請在此執行快照。導入 你無需在轉接器中使用這個方法。onLayout()- 每次 使用者變更了列印設定,例如不同大小的頁面 或頁面方向,讓應用程式有機會計算 。這個方法至少須傳回預期頁數 。onWrite()- 呼叫後才能顯示列印 轉換成要列印的檔案。每次呼叫此方法後,系統可能會多次呼叫這個方法onLayout()呼叫。onFinish()- 結束時呼叫一次 列印程序的第一步如果您的應用程式有一次性拆解工作 來執行您不需要在轉接程式中導入這個方法。
以下各節說明如何實作版面配置和寫入方法,各 但這對列印轉接器的運作最為重要。
注意:系統會在應用程式的主執行緒呼叫這些轉接器方法。如果
預期執行這些方法時,
請實作這些程式碼,以便在單獨的執行緒中執行。舉例來說,您可以將
將文件版面配置或列印文件寫入個別 AsyncTask 物件中。
Compute 列印文件資訊
在 PrintDocumentAdapter 類別的實作中,
應用程式必須能夠指定其建立的文件類型,以及計算
列印工作頁數,提供列印頁面大小相關資訊。
實作 onLayout() 方法的
轉接程式會進行這些計算,並提供
在 PrintDocumentInfo 類別中列印工作,包括頁數和
內容類型以下程式碼範例顯示 PrintDocumentAdapter 的 onLayout() 方法基本實作:
Kotlin
override fun onLayout( oldAttributes: PrintAttributes?, newAttributes: PrintAttributes, cancellationSignal: CancellationSignal?, callback: LayoutResultCallback, extras: Bundle? ) { // Create a new PdfDocument with the requested page attributes pdfDocument = PrintedPdfDocument(activity, newAttributes) // Respond to cancellation request if (cancellationSignal?.isCanceled == true) { callback.onLayoutCancelled() return } // Compute the expected number of printed pages val pages = computePageCount(newAttributes) if (pages > 0) { // Return print information to print framework PrintDocumentInfo.Builder("print_output.pdf") .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) .setPageCount(pages) .build() .also { info -> // Content layout reflow is complete callback.onLayoutFinished(info, true) } } else { // Otherwise report an error to the print framework callback.onLayoutFailed("Page count calculation failed.") } }
Java
@Override public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle metadata) { // Create a new PdfDocument with the requested page attributes pdfDocument = new PrintedPdfDocument(getActivity(), newAttributes); // Respond to cancellation request if (cancellationSignal.isCanceled() ) { 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() 方法的執行結果可以
會產生三種結果:如果計算所需費用,例如已完成、取消或失敗
就無法完成版面配置您必須呼叫適當的
PrintDocumentAdapter.LayoutResultCallback 物件的方法。
注意:
onLayoutFinished() 方法會指出版面配置內容是否已實際變更
。正確設定這個參數可讓列印架構避免
不需要呼叫 onWrite() 方法
基本上,快取先前編寫的列印文件並改善效能。
「onLayout()」的主要工作是
根據印表機屬性計算預期輸出的頁數。
這個數字的計算方式與應用程式版面配置頁面的方式有極大影響
以及列印方式以下程式碼範例呈現的導入程序包含網頁數量:
取決於列印方向:
Kotlin
private fun computePageCount(printAttributes: PrintAttributes): Int { var itemsPerPage = 4 // default item count for portrait mode val pageSize = printAttributes.mediaSize if (!pageSize.isPortrait) { // Six items per page in landscape orientation itemsPerPage = 6 } // Determine number of print items val printItemCount: Int = getPrintItemCount() return Math.ceil((printItemCount / itemsPerPage.toDouble())).toInt() }
Java
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); }
寫入列印文件檔案
當您需要將列印輸出寫入檔案時,Android 列印架構會呼叫應用程式 PrintDocumentAdapter 類別的 onWrite() 方法。方法的參數可指定要在
和要使用的輸出檔案實作這個方法後,必須讓每個
。這項程序完成後,
呼叫回呼物件的 onWriteFinished() 方法。
注意:Android 列印架構可能會在每次需要時,呼叫 onWrite() 方法一次或多次
呼叫 onLayout()。因此
請務必設定
在列印內容版面配置尚未變更的情況下,將 onLayoutFinished() 方法變更為 false。
,避免不必要的重寫列印文件。
注意:
onLayoutFinished() 方法會指出版面配置內容是否已實際變更
。正確設定這個參數可讓列印架構避免
不需要呼叫 onLayout() 方法
基本上,快取先前編寫的列印文件並改善效能。
以下範例說明如何使用 PrintedPdfDocument 類別建立 PDF 檔案,示範這項程序的基本機制:
Kotlin
override fun onWrite( pageRanges: Array<out PageRange>, destination: ParcelFileDescriptor, cancellationSignal: CancellationSignal?, callback: WriteResultCallback ) { // Iterate over each page of the document, // check if it's in the output range. for (i in 0 until totalPages) { // 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?.startPage(i)?.also { page -> // check for cancellation if (cancellationSignal?.isCanceled == true) { callback.onWriteCancelled() pdfDocument?.close() pdfDocument = null return } // Draw page content for printing drawPage(page) // Rendering is complete, so page can be finalized. pdfDocument?.finishPage(page) } } } // Write PDF document to file try { pdfDocument?.writeTo(FileOutputStream(destination.fileDescriptor)) } catch (e: IOException) { callback.onWriteFailed(e.toString()) return } finally { pdfDocument?.close() pdfDocument = null } val writtenPages = computeWrittenPages() // Signal the print framework the document is complete callback.onWriteFinished(writtenPages) ... }
Java
@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 = pdfDocument.startPage(i); // check for cancellation if (cancellationSignal.isCanceled()) { callback.onWriteCancelled(); pdfDocument.close(); pdfDocument = null; return; } // Draw page content for printing drawPage(page); // Rendering is complete, so page can be finalized. pdfDocument.finishPage(page); } } // Write PDF document to file try { pdfDocument.writeTo(new FileOutputStream( destination.getFileDescriptor())); } catch (IOException e) { callback.onWriteFailed(e.toString()); return; } finally { pdfDocument.close(); pdfDocument = null; } PageRange[] writtenPages = computeWrittenPages(); // Signal the print framework the document is complete callback.onWriteFinished(writtenPages); ... }
這個範例會將 PDF 網頁內容的轉譯作業委派給 drawPage()
方法,我們將在下一節討論。
和版面配置一樣,執行作業 onWrite()
方法可能會有三種結果:完成、取消或失敗
因此無法撰寫您必須呼叫
對 PrintDocumentAdapter.WriteResultCallback 物件的適當方法執行。
注意:轉譯要列印的文件是一項需要大量資源的作業。於
以避免阻斷應用程式的主要使用者介面執行緒
舉例來說,在獨立執行緒中執行網頁轉譯和寫入作業等
在 AsyncTask 中。
如需進一步瞭解如何使用非同步工作等執行執行緒,
請參閱程序
和 Threads
繪製 PDF 網頁內容
您的申請列印時,應用程式必須產生 PDF 文件,並傳遞至
以及用於列印的 Android 列印架構你可以使用任何 PDF 生成程式庫
發展路徑可能包括
縮小技術提議用途的範圍本課程說明如何使用 PrintedPdfDocument 類別
來產生 PDF 頁面內容
PrintedPdfDocument 類別使用 Canvas
物件,在 PDF 頁面上繪製元素,與活動版面配置中的繪圖類似。你可以畫出
元素。Canvas下列
程式碼範例則示範如何使用這些範本,在 PDF 文件頁面中繪製一些簡易元素
方法:
Kotlin
private fun drawPage(page: PdfDocument.Page) { page.canvas.apply { // units are in points (1/72 of an inch) val titleBaseLine = 72f val leftMargin = 54f val paint = Paint() paint.color = Color.BLACK paint.textSize = 36f drawText("Test Title", leftMargin, titleBaseLine, paint) paint.textSize = 11f drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint) paint.color = Color.BLUE drawRect(100f, 100f, 172f, 172f, paint) } }
Java
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); }
使用 Canvas 在 PDF 網頁中繪圖時,元素會指定
點,也就是 1/72 英寸的請務必使用這個測量單位來指定大小
各個元素繪製元素時,座標系統從 0,0 開始
。
提示:Canvas 物件可讓您放置列印內容
位於 PDF 文件邊緣,許多印表機無法列印文件邊緣
像是用實體紙張在載入過程中,請務必考量頁面無法列印的邊緣。
您可以使用這個類別建立列印文件