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文档的边缘,但许多打印机不能打印到物理纸张的边缘。 在使用此类构建打印文档时,请确保考虑了页面的不可打印边缘。