列印自訂文件

部分應用程式 (例如繪圖應用程式、頁面版面配置應用程式和主要聚焦應用程式) 產生精美的列印頁面是一大特色在本例中 列印圖片或 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 類別中列印工作,包括頁數和 內容類型以下程式碼範例顯示 PrintDocumentAdapteronLayout() 方法基本實作:

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 文件邊緣,許多印表機無法列印文件邊緣 像是用實體紙張在載入過程中,請務必考量頁面無法列印的邊緣。 您可以使用這個類別建立列印文件