部分應用程式 (例如繪圖應用程式、頁面版面配置應用程式和主要聚焦應用程式) 產生精美的列印頁面是一大特色在本例中 列印圖片或 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 文件邊緣,許多印表機無法列印文件邊緣
像是用實體紙張在載入過程中,請務必考量頁面無法列印的邊緣。
您可以使用這個類別建立列印文件