作者:郭孝星
微博:郭孝星的新浪微博
邮箱:[email protected]
博客:http://blog.csdn.net/allenwells
Github:https://github.com/AllenWells
在Android 4.4以及更高版本的系统中,提供了直接从Android应用程序打印图片和文字的服务,包括打印图片、HTML页面以及创建自定义打印文档等。
一 打印图片
Android Support Library中的PrintHelper提供了一种打印图片的简单方法,该类有一个单一的布局选项:setScaleMode,它允许提供以下选项:
- SCALE_MODE_FIT:该选项会调整图片大小,整个图片就会在打印有效区域内全部显示出来。
- SCALE_MODE_FILL:该选项会等比例地调整图片的大小使得图片可以充满整个打印有效区域,这种模式下,图片的一部分可能无法打印出来,如果不设置图片的打印布局选项,该模式将是默认的图片拉伸方式。
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);}
上述方法被调用之后,Android的打印界面就会出现,允许用户选择一个打印机和它的打印机选项,用户可以选择打印图片和取消操作,当打印开始后,系统的通知栏里会显示一个打印通知提醒。
二 打印HTML文档
Android 4.4以后提供了WebView类,该类可以加载一个本地HTML资源或者从网上下载一个页面,然后创建打印任务,并把它交给Android打印服务。
2.1 加载打印文档
WebView对象一般作为Activity布局的一部分,如果应用当前没有使用WebView,我们可以创建一个该类的实例,以便进行打印,步骤如下:
- 在HTML资源加载完毕后,创建一个WebViewClient用来启动一个打印任务。
- 加载HTML资源到WebView对象中。
举例
打印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); //在WebViewClient中的onPageFinished()方法内调用创建打印任务的方法, //以便页面加载完毕后再进行打印,否则会导致打印输出不完整或空白。 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); //保留了一个WebView对象实例的引用,这样可以保证它不会在打印任务创建之前被垃圾回收器所回收。 mWebView = webView;}
如果我们想打印图像,而这个图像文件在工程的assets目录下,方法如下所示:
webView.loadDataWithBaseURL("file://android_asset/images/", htmlBody, "test/HTML", "UTF-8", null);
如果我们想加载并打印一个网页,方法如下所示:
webView.loadUrl("http://blog.csdn.net/allenwells");
注意:当使用WebView打印文档时,有以下限制:
- 不能为文档添加页眉和页脚,包括页号。
- HTML文档的打印选项不包含选择打印的页数范围,例如:对于一个10页的HTMl文档,只打印2到4页是不可以
的。 - 一个WebView的实例只能在同一时间处理一个打印任务。
- 若一个HTML文档包含CSS打印属性,比如一个landscape属性,这是不被支持的。
- 不能通过一个HTML文档中的JavaScript脚本来激活打印。
2.2 创建打印任务
当HTML页面加载完毕后,我们就可以创建打印任务了,如下所示:
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 printJob = printManager.print(jobName, printAdapter, new PrintAttributes.Builder().build()); // Save the job object for later status checking mPrintJobs.add(printJob);}
三 打印自定义文档
对于有些应用,比如绘图应用,页面布局应用和其它一些关注于图像输出的应用,创造出美丽的打印页面将是它的核心功能。在这种情况下,仅仅打印一幅图片或一个HTML文档就不够了。这类应用的打印输出需要精确地控制每一个会在页面中显示的对象,包括字体,文本流,分页符,页眉,页脚和一些图像元素等等。
为了能够创建自定义打印文档,我们需要构建和打印框架可以相互通信的组件,调整打印参数,绘制页面元素并管理多个页面的打印。
3.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"; //传递PrintDocumentAdapter,开始打印任务。该print()方法最后一个参数接收的是 //一个PrintAttributes对象,我们可以使用这个参数向打印框架进行一些打印设置,比 //如基于前一个打印周期的预设,从而改善用户体验。 printManager.print(jobName, new MyPrintDocumentAdapter(getActivity()), null); }
3.2 创建打印适配器
打印适配器负责与Android打印框架交互并处理打印过程的每一步。这个过程需要用户在创建打印文档前选择打印机和打印选项。由于用户可以选择不同性能的打印机,不同的页面尺寸或不同的页面方向,因此这些选项可能会影响最终的打印效果。当这些选项配置好之后,打印框架会寻求适配器进行布局并生成一个打印文档,以此作为打印的前期准备。一旦用户点击了打印按钮,框架会将最终的打印文档传递给一个打印提供程序(Print Provider)供打印输出。在打印过程中,用户可以选择取消打印,所以打印适配器必须监听并响应取消打印的请求。
PrintDocumentAdapter抽象类负责处理打印的生命周期,它的回调方法如下所示:
- onStart():一旦打印进程开始,该方法就将被调用。如果应用有任何一次性的准备任务要执行,比如获取一个要打印数据的快照,那么让它们在此处执行。在适配器中,这个回调方法不是必须实现的。
- onLayout():每当用户改变了影响打印输出的设置时,例如改变了页面的尺寸,或者页面的方向,该函数将会被调用,以此给应用一个机会去重新计算打印页面的布局。另外,该方法必须返回打印文档包含多少页面。
- onWrite():该方法调用后,会将打印页面渲染成一个待打印的文件。该方法可以在onLayout()方法被调用后调用一次或多次。
- onFinish():一旦打印进程结束后,该方法将会被调用。如果应用有任何一次性销毁任务要执行,让这些任务在该方法内执行。这个回调方法不是必须实现的。
注意:这些适配器的回调方法会在应用的主线程上被调用。如果这些方法的实现在执行时可能需要花费大量的时间,那么我们可以把它们放到另一个线程里执行。例如:我们可以将布局或者写入打印文档的操作封装在一个AsyncTask对象中。
3.3 计算打印文档信息
在实现PrintDocumentAdapter类时,应用必须能够指定创建文档的类型,计算打印任务所需要打印的总页数,并提供打印页面的尺寸信息。
举例
onLayout()方法的实现
//onLayout()方法执行结果有3种完成、取消或失败,我们需要通过PrintDocumentAdapter.LayoutResultCallBack//对象中的适当方法来指出这些结构中的一个。@Overridepublic 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."); }}
//计算打印文档的页数,并把它作为打印参数交给打印机,打印页数是根据打印方向确定的。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);}
2.4 将打印文档写入文件
当将打印内容输出到一个文件是,Android打印会调用PrintDocumentAdapter类的onWrite()方法,这个方法的参数指定了哪些页面要被写入以及要使用的输出文件,该方法的实现必须将每一个请求页的内容渲染成一个含有多个页面的PDF文件,当这个过程结束以后,我们需要调用callback对象的onWriteFinished()方法。
举例
onWrite()方法的实现。
//onWrite()方法执行结果有3种完成、取消或失败,我们需要通过PrintDocumentAdapter.LayoutResultCallBack//对象中的适当方法来指出这些结构中的一个。@Overridepublic 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); ...}
2.5 绘制PDF页面的内容
当应用进行打印时,我们需要生成一个PDF文档并将它传递给Android打印框架以进行打印,我们可以借助任何PDF生成库来协助完成这个操作。
PrintedPdfDocument类使用Canvas对象来在PDF页面上绘制元素。
举例
在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文档的边缘,但是很多打印机无法再纸张的边缘进行打印,因此我们使用PrintedPdfDocument构建打印文档时,应该保证考虑到了哪些无法打印的边缘区域。
版权声明:当我们认真的去做一件事的时候,就能发现其中的无穷乐趣,丰富多彩的技术宛如路上的风景,边走边欣赏。