Android用戶經常只在自己的設備上查看內容,但有時顯示某人的屏幕不是一種充分的方式來共享信息。 您可以從Android應用程式列印資訊,讓使用者可以透過應用程式查看更大版本的內容,或與未使用您應用程式的使用者分享。 打印還允許他們創建不依賴於具有設備,足夠的電池電量或無線網絡連接的信息的快照。
在Android 4.4(API級別19)及更高版本中,框架提供了直接從Android應用程序打印圖像和文檔的服務。
本培訓介紹如何在應用程序中啓用打印功能,包括打印圖像,HTML頁面和創建自定義文檔以進行打印。
一:打印照片
拍攝和分享照片是移動設備最流行的用途之一。 如果應用程序拍攝照片,顯示照片或允許用戶共享圖像,則應考慮在應用程序中啓用打印這些圖像。 Android Support Library 提供了一個方便的功能,使用最少的代碼和簡單的打印佈局選項集啓用圖像打印。
本節將向您介紹如何使用v4支持庫PrintHelper
類打印圖像:
Android支持庫PrintHelper類提供了一種打印圖像的簡單方法。 該類有一個單一的佈局選項,setScaleMode(),它允許您打印兩個選項之一:
SCALE_MODE_FIT - 此選項調整圖像大小,以便整個圖像顯示在頁面的可打印區域內。
SCALE_MODE_FILL - 此選項縮放圖像,以便它填充頁面的整個可打印區域。 選擇此設置意味着不打印圖像的頂部和底部,或左右邊緣的某些部分。 如果未設置縮放模式,則此選項爲默認值。
setScaleMode()的縮放選項保持圖像的現有寬高比不變。 以下代碼示例顯示如何創建PrintHelper類的實例,設置縮放選項,並開始打印過程:
private void doPhotoPrint() { PrintHelper photoPrinter = new PrintHelper(getActivity()); photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.droids); photoPrinter.printBitmap("droids.jpg - test print", bitmap); }此方法可以稱爲菜單項的操作。 注意,不總是支持的操作(例如打印)的菜單項應該放在溢出菜單中。 有關更多信息,請參閱Action Bar設計指南。
調用printBitmap()方法後,不需要從應用程序執行進一步的操作。 將顯示Android打印用戶界面,允許用戶選擇打印機和打印選項。 然後用戶可以打印圖像或取消操作。 如果用戶選擇打印圖像,則會創建打印作業,並在系統欄中顯示打印通知。
如果要在打印輸出中包括除圖像之外的其他內容,則必須構建打印文檔。 有關創建要打印的文檔的信息,請往下看。
二:打印HTML文檔
在Android上打印超出簡單照片的內容需要在打印文檔中合成文本和圖形。 Android框架提供了一種使用HTML來編寫文檔並使用最少的代碼打印文檔的方法。
在Android 4.4(API級別19)中,WebView類已更新爲啓用打印HTML內容。
該類允許您加載本地HTML資源或從網絡下載頁面,創建打印作業並將其移交給Android的打印服務。
本節將向您展示如何快速構建包含文本和圖形的HTML文檔,並使用WebView進行打印。
1.加載HTML文檔
使用WebView打印HTML文檔涉及加載HTML資源或將HTML文檔構建爲字符串。 本節介紹如何構建HTML字符串並將其加載到WebView以進行打印。
此視圖對象通常用作活動佈局的一部分。 但是,如果您的應用程序沒有使用WebView,您可以專門爲打印目的創建類的實例。 創建此自定義打印視圖的主要步驟如下:
(1)創建在加載HTML資源後啓動打印作業的WebViewClient。
(2)將HTML資源加載到WebView對象中。
下面的代碼示例演示瞭如何創建一個簡單的WebViewClient並加載一個動態創建的HTML文檔:
private WebView mWebView; private void doWebViewPrint() { // Create a WebView object specifically for printing WebView webView = new WebView(getActivity()); webView.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading(WebView view, String url) { return false; } @Override public void onPageFinished(WebView view, String url) { Log.i(TAG, "page finished loading " + url); createWebPrintJob(view); mWebView = null; } }); // Generate an HTML document on the fly: String htmlDocument = "<html><body><h1>Test Content</h1><p>Testing, " + "testing, testing...</p></body></html>"; webView.loadDataWithBaseURL(null, htmlDocument, "text/HTML", "UTF-8", null); // Keep a reference to WebView object until you pass the PrintDocumentAdapter // to the PrintManager mWebView = webView; }
注意:確保生成打印作業的調用發生在您在上一節中創建的WebViewClient的onPageFinished()方法中。
如果不等到頁面加載完成,打印輸出可能不完整或空白,或可能完全失敗。
注意:上面的示例代碼保存了一個WebView對象的實例,因此在創建打印作業之前不會對其進行垃圾回收。 確保您在自己的實現中執行相同操作,否則打印過程可能會失敗。
如果要在頁面中包含圖形,請將圖形文件放在項目的assets /目錄中,並在loadDataWithBaseURL()方法的第一個參數中指定基本URL,如以下代碼示例所示:
webView.loadDataWithBaseURL("file:///android_asset/images/", htmlBody, "text/HTML", "UTF-8", null);您還可以通過使用loadUrl()替換loadDataWithBaseURL()方法來加載要打印的網頁,如下所示。
// Print an existing web page (remember to request INTERNET permission!): webView.loadUrl("http://developer.android.com/about/index.html");
使用WebView創建打印文檔時,應注意以下限制:
(1)您無法向文檔中添加頁眉或頁腳(包括頁碼)。
(2)HTML文檔的打印選項不包括打印頁面範圍的功能,例如:不支持打印10頁HTML文檔的第2頁到第4頁。
(3)WebView的一個實例一次只能處理一個打印作業。
(4)不支持包含CSS打印屬性(如橫向屬性)的HTML文檔。
(5)您不能在HTML文檔中使用JavaScript來觸發打印。
注意:包含在佈局中的WebView對象的內容也可以在加載文檔後打印。
如果要創建更定製的打印輸出並完全控制打印頁上的內容,請看下一節:Printing
a Custom Document
2.創建打印作業
在創建WebView並加載HTML內容後,您的應用程序幾乎完成了它的打印過程的一部分。 接下來的步驟是訪問PrintManager,創建打印適配器,最後創建打印作業。
以下示例說明如何執行這些步驟:
private void createWebPrintJob(WebView webView) { // Get a PrintManager instance PrintManager printManager = (PrintManager) getActivity() .getSystemService(Context.PRINT_SERVICE); // Get a print adapter instance PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(); // Create a print job with name and adapter instance String jobName = getString(R.string.app_name) + " Document"; PrintJob printJob = printManager.print(jobName, printAdapter, new PrintAttributes.Builder().build()); // Save the job object for later status checking mPrintJobs.add(printJob); }此示例保存PrintJob對象的實例以供應用程序使用,這不是必需的。 您的應用程序可能會使用此對象來跟蹤打印作業正在處理時的進度。 當您希望在應用程序中監視打印作業的完成,失敗或用戶取消操作的狀態時,此方法非常有用。 不需要創建應用程序內通知,因爲打印框架會自動爲打印作業創建系統通知。
三:打印自定義文檔
對於一些應用程序,如繪圖應用程序,頁面佈局應用程序和其他專注於圖形輸出的應用程序,創建漂亮的打印頁面是一個關鍵特性。 在這種情況下,打印圖像或HTML文檔是不夠的。 這些類型應用程序的打印輸出需要精確控制進入頁面的所有內容,包括字體,文本流,分頁符,頁眉,頁腳和圖形元素。
爲您的應用程序創建完全自定義的打印輸出需要比前面討論的方法更多的編程投資。 您必須構建與打印框架通信的組件,調整爲打印機設置,繪製頁面元素和管理多個頁面上的打印。
本節將向您介紹如何與打印管理器連接,創建打印適配器並構建打印內容
1.連接到打印管理器
當應用程序直接管理打印過程時,從用戶收到打印請求後的第一步是連接到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類的實例,該類負責處理打印生命週期的步驟。
打印適配器類的實現將在下一節中討論。
注意:print()方法中的最後一個參數接受一個PrintAttributes對象。
您可以使用此參數向打印框架提供提示,並根據先前的打印週期提供預設選項,從而改善用戶體驗。 您還可以使用此參數設置更適合正在打印的內容的選項,例如在打印處於該方向的照片時將方向設置爲橫向
2.創建打印適配器
打印適配器與Android打印框架交互並處理打印過程的步驟。此過程要求用戶在創建要打印的文檔之前選擇打印機和打印選項。這些選擇可以影響最終輸出,因爲用戶選擇具有不同輸出能力,不同頁面大小或不同頁面方向的打印機。當進行這些選擇時,打印框架要求適配器佈置並生成打印文檔,以準備最終輸出。一旦用戶點擊打印按鈕,框架獲取最終打印文檔並將其傳遞給打印提供者用於輸出。在打印過程中,用戶可以選擇取消打印操作,因此打印適配器還必須監聽並對取消請求作出反應。
PrintDocumentAdapter抽象類設計用於處理打印生命週期,它有四個主要的回調方法。您必須在打印適配器中實現這些方法,以便與打印框架正確交互:
(1)onStart() - 在打印進程的開始調用一次。如果應用程序有任何一次性準備任務要執行,例如獲取要打印的數據的快照,請在此處執行。不需要在適配器中實現此方法。
(2)onLayout() - 每次用戶更改影響輸出的打印設置(例如不同的頁面大小或頁面方向)時調用,爲應用程序提供計算要打印的頁面佈局的機會。至少,此方法必須返回打印文檔中預期的頁數。
(3)onWrite() - 調用將打印的頁面轉換爲要打印的文件。此方法可以在每次onLayout()調用後調用一次或多次。
(4)onFinish() - 在打印過程結束時調用一次。如果您的應用程序有任何一次性拆卸任務要執行,請在此處執行。不需要在適配器中實現此方法。
以下部分介紹如何實現佈局和寫入方法,這對於打印適配器的功能至關重要。
注意:這些適配器方法在應用程序的主線程上調用。如果你期望這些方法在你的實現中的執行需要大量的時間,實現它們在一個單獨的線程中執行。例如,您可以封裝佈局或在單獨的AsyncTask對象中打印文檔編寫工作
3.計算打印文檔信息
在PrintDocumentAdapter類的實現中,您的應用程序必須能夠指定正在創建的文檔類型,並計算打印作業的總頁數,並提供有關打印頁面大小的信息。 在適配器中實現onLayout()方法進行這些計算,並提供有關PrintDocumentInfo類中打印作業的預期輸出的信息,包括頁數和內容類型。
以下代碼示例顯示了PrintDocumentAdapter的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對象的相應方法來指定其中一個結果。
注意:onLayoutFinished()方法的boolean參數指示自上次請求以來佈局內容是否實際已更改。
正確設置此參數允許打印框架避免不必要地調用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); }
4.編寫打印文檔文件
當需要將打印輸出寫入文件時,Android打印框架調用應用程序的PrintDocumentAdapter類的onWrite()方法。方法的參數指定應該寫入哪些頁面以及要使用的輸出文件。然後,您的此方法的實現必須將每個請求的內容頁面呈現爲多頁PDF文檔文件。當此過程完成時,您調用回調對象的onWriteFinished()方法。
注意:Android打印框架可以每次調用onLayout()時調用onWrite()方法一次或多次。爲此,當打印內容佈局未改變時,重要的是將onLayoutFinished()方法的布爾參數設置爲false,以避免打印文檔的不必要的重寫。
注意:onLayoutFinished()方法的布爾參數指示自上次請求以來佈局內容是否實際已更改。正確設置此參數允許打印框架避免不必要地調用onLayout()方法,基本上緩存先前寫入的打印文檔並提高性能。
以下示例演示了使用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()方法,這將在下一節中討論。
與佈局一樣,onWrite()方法的執行可以有三個結果:完成,取消或在內容不能寫入的情況下失敗。 您必須通過調用PrintDocumentAdapter.WriteResultCallback對象的相應方法指定其中一個結果。
注意:渲染文檔以進行打印可能是資源密集型操作。 爲了避免阻塞應用程序的主用戶界面線程,您應該考慮在單獨的線程上執行頁面呈現和寫入操作,例如在AsyncTask中。 有關使用異步任務等執行線程的更多信息,請參閱 Processes
and Threads
5.繪圖PDF頁面內容
當應用程序打印時,應用程序必須生成PDF文檔並將其傳遞到Android打印框架進行打印。 您可以使用任何PDF生成庫用於此目的。 本課將介紹如何使用PrintedPdfDocument類根據內容生成PDF頁面。
PrintedPdfDocument類使用Canvas對象在PDF頁面上繪製元素,類似於在活動佈局上繪圖。 您可以使用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頁面上繪製時,元素以點爲單位指定,即1/72英寸。 請確保使用此度量單位來指定頁面上元素的大小。 對於繪製元素的定位,座標系從頁面的左上角的0,0開始。
提示:Canvas對象允許您將打印元素放置在PDF文檔的邊緣,但許多打印機不能打印到物理紙張的邊緣。 在使用此類構建打印文檔時,請確保考慮了頁面的不可打印邊緣。