Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android官方開發文檔Training系列課程中文版:打印內容之自定義文檔打印

Android官方開發文檔Training系列課程中文版:打印內容之自定義文檔打印

編輯:關於Android編程

對於一些應用,比如繪圖類APP,版面設計類APP以及其它APP,這些APP都關注圖形的輸出,有一個漂亮的打印頁面是它們的關鍵特性。在這種情況下,就不單單是打印一張圖片或者是HTML文檔這麼簡單了。這些程序對於這種類型的打印需要對頁面中每樣事物的控制都特別的精細,包括字體、文本流、頁面間距、頁眉、頁腳以及圖形元素。

創建打印輸出對於程序來說是完全自定義的,這需要更多設計上的投入,就像上面討論的那樣。你必須構建一些可以與打印框架交流的組件,並且還可以用來調整打印設置,繪制頁面元素及管理多個頁面的打印。

這節課展示了如何與打印管理者進行連接、創建打印適配器和構建打印內容。

連接打印管理者

當程序需要直接管理打印進程時,在收到用戶的打印請求之後,第一步就是連接Android的打印框架,以及操作PrintManager類的實例。這個類允許你實例化一個打印工作並開始打印的生命過程。下面的代碼展示了如何獲得一個打印管理者和啟動打印進程。

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的實例,這個對象可以處理打印過程的每一個步驟。打印適配器的實現會在下面的章節中討論到。

Note: print()方法的最後一個參數需要一個PrintAttributes對象。你可以使用這個參數給打印框架提供一些提示及給原先的打印周期預先設置一些選項,這可以改進用戶體驗。你還可以使用這個參數來設置一些選項,這些選項更適用於內容的打印,比如當打印照片的時候可以設置打印的方向為照片本身的方向。

創建打印適配器

打印適配器會與Android的打印框架相連接,並會處理打印過程的每一個步驟。這個過程要求用戶在創建文檔打印之前選擇打印機及相關的打印選項。這些過程會影響最終的輸出結果,就像用戶選擇了不同打印能力,不同的頁面尺寸,不同的頁面方向一樣。隨著這些選項的設置,打印框架會要求適配器展示並生成一個打印文稿,為最終的打印做准備。一旦用戶按下了打印按鈕,打印框架會拿到最終的打印文檔然後交付給打印提供者以便打印。在打印的過程中,用戶可以選擇取消打印行為,所以打印適配器必須監聽並響應取消請求。

抽象類PrintDocumentAdapter被設計為用來處理打印過程的生命周期,它擁有4個主要的回調方法。你必須在打印適配器中實現這些方法,以便可以與打印框架進行適當的交互:

onStart() - 會在打印進程開始的時候調用一次,如果應用對任務有任何的單次預處理任務,比如獲取要打印的數據段,就可以在這裡執行。實現這個方法並不是必須要求的。 onLayout() - 會在用戶每次更改打印設置的時候調用一次,這會影響到最終的輸出結果,比如不同的頁面尺寸,或者頁面方向,提供給程序一個機會來估算頁面的版面。在最低限度下,這個方法必須返回將要打印的文檔有多少頁。 onWrite() - 該方法被用來將要打印的頁面作用到一個文件中,然後再被打印。這個方法可能會在onLayout()方法每次調用之後被調用一次或者多次, onFinish() - 該方法會在打印過程結束的時候調用一次。如果程序對打印任務需要任何的銷毀工作,那可以在這裡執行。這個方法不是必須要求被實現的。

下面的章節會描述如何實現layout和write方法,這兩個方法實現了打印適配器的決定性功能。

Note: 適配器方法會在程序的主線程中被調用。如果你認為執行這些方法會消耗大量時間的話,那麼可以在單獨的線程中實現它們。舉個例子,你可以將layout或者打印文檔的writing工作放入單獨的AsyncTask對象中。

計算文檔信息

在PrintDocumentAdapter的實現中,程序必須指定文檔的類型,它還需要創建並計算打印工作的總頁數,獲得被打印頁的尺寸信息。onLayout()方法應該進行這些計算並且將要輸出的文檔信息放入一個PrintDocumentInfo對象中,包括頁面數量以及內容類型。下面的代碼展示onLayout()方法的最基礎實現:

@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()的執行會有三個結果:完成、取消或者失敗,失敗的情況就是說不能夠完成版面的計算。你必須通過調用PrintDocumentAdapter.LayoutResultCallback對象的適當方法來指定其中一個結果。

Note: onLayoutFinished()方法的布爾參數指示了從最後一次請求開始版面的內容是否有實質上的改變。適當的設置這個參數可以允許打印框架避免對onWrite()方法進行不必要的調用,實質上會緩存原先的書面打印文檔並改善性能。

onLayout()的主要工作是計算頁碼,這個頁面會作為打印機的輸出屬性。至於如何計算頁碼這高度依賴程序如何排版打印頁。下面的代碼展示了一個實現,這個實現的頁碼取決於打印的方向:

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打印框架會調用onWrite()方法。這個方法的參數指明了哪一頁需要被寫入以及被使用到的輸出文件。你的實現必須將每個請求內容頁渲染到一個多頁的PDF文檔文件中。這個過程完成以後,你需要調用回調對象的onWriteFinished()方法。

Note: 由於Android打印框架可能會在每次調用onLayout()方法之後調用若干次onWrite()方法,所以在打印頁面並沒有發生實質上的改變時設置onLayoutFinished()方法的布爾參數為false是很重要的,這樣可以避免對打印文檔進行不必要的重復寫入。

Note: onLayoutFinished()方法的布爾參數指示了從最後一次請求開始版面的內容是否有實質上的改變。適當的設置這個參數可以允許打印框架避免對onWrite()方法進行不必要的調用,實質上會緩存原先的書面打印文檔並改善性能。

下面簡要演示了使用PrintedPdfDocument類創建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()方法,這會在下面的章節中討論。

和layout一樣,onWrite()的執行過程也有三個結果:完成、取消或是失敗。在失敗情況下不能再寫入內容。你必須通過PrintDocumentAdapter.WriteResultCallback對象的適當方法指明結果。

Note: 文檔打印的過程是個資源密集型的操作。為了避免阻塞UI線程,你應該考慮在單獨的線程中執行這些事情,比如在AsyncTask中。有關更多關於比如異步任務的工作執行線程,請參見 Processes and Threads。

繪制PDF頁面內容

當程序要打印時,程序必須先生成一個PDF文檔,然後將文檔交付給Android打印框架來打印。你可以使用任何的PDF生成庫來實現這個目的。這節課展示了如何使用PrintedPdfDocument類來生成PDF頁。

PrintedPdfDocument類使用了一個Canvas對象來繪制元素到PDF頁上,這與Activity的布局繪制很類似。你可以使用Canvas的繪制方法來繪制頁面元素。下面的代碼演示了如何使用這些方法來繪制一些簡單的元素到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);
}

當使用Canvas繪制PDF頁面時,元素由一些點來指定位置,這個點的大小是英寸的72分之一。要確保使用這個測量單位來指明元素的尺寸。對於繪制元素的定位,坐標系統會從頁面的左上角0,0點開始。

Tip: 雖然Canvas對象允許你將打印元素放置到PDF文檔的邊上,但很多打印機並沒有能力可以將邊上的元素打印到紙上去。要確保在使用這個類構建打印文檔時要保留一定的頁面邊距。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved