【Android应用开发技术:媒体开发】打印

作者:郭孝星

微博:郭孝星的新浪微博

邮箱:[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,我们可以创建一个该类的实例,以便进行打印,步骤如下:

  1. 在HTML资源加载完毕后,创建一个WebViewClient用来启动一个打印任务。
  2. 加载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
//对象中的适当方法来指出这些结构中的一个。
@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.");
    }
}
//计算打印文档的页数,并把它作为打印参数交给打印机,打印页数是根据打印方向确定的。
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
//对象中的适当方法来指出这些结构中的一个。
@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);
    ...
}

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构建打印文档时,应该保证考虑到了哪些无法打印的边缘区域。

版权声明:当我们认真的去做一件事的时候,就能发现其中的无穷乐趣。作为程序员的我们,世界也更加简单,丰富多彩的技术宛如路上的风景,边走边欣赏。

时间: 2024-08-27 13:05:29

【Android应用开发技术:媒体开发】打印的相关文章

本人讲课时录制的Android应用开发技术教学视频

网盘地址:http://yun.baidu.com/pcloud/album/info?query_uk=1963923831&album_id=3523786484935252365 本人讲课时录制的视频,采用webex录制,视频文件内容相对较小30-50兆左右,1个视频文件平均大概有1个小时左右的时间,每个例子基本上从建立项目开始边做边讲. 由于讲课范围是Android应用开发技术,视频没涉及搭建环境,基础控件的使用等基础内容. 主要内容包括: 后台服务. 服务的绑定.服务和线程.远程服务和

【Android应用开发技术:用户界面】章节列表

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:用户界面]章节列表 [Android应用开发技术:用户界面]用户界面基本原理 [Android应用开发技术:用户界面]设备适配 [Android应用开发技术:用户界面]用户界面布局技巧 [Android应用开发技术:用户界面]View基本原理 [

【Android应用开发技术:图像处理】Bitmap显示性能优化分析

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:图像处理]章节列表 Bitmap经常会消耗大量内存而导致程序崩溃,常见的异常如下所示:java.lang.OutofMemoryError:bitmap size extends VM budget,因此为了保证程序的稳定性,我们应该小心处理程序

【Android应用开发技术:用户界面】界面设计中易混淆的概念汇总

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:用户界面]章节列表 一 px.dp.sp px:即像素,每个px对应屏幕上的一个点. dp:即设备独立像素,一种基于屏幕密度的抽象单位,在每英寸160点的显示器上:1 dp = 1 px. sp:即比例像素,主要用来处理字体大小,可以根据用户字体

【Android应用开发技术:用户界面】9Patch图片设计

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:用户界面]章节列表 9Patch图片是一种特殊的PNG图片,该图片以.9.png为后缀名,它在原始图片四周各添加一个宽度为1像素的线条,这4条线决定了该图片的缩放规则和内容显示格则. 一 9Patch图片的显示规则 9Patch图片left边和t

【Android应用开发技术:网络通信】网络服务可发现基本原理

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:网络通信]章节列表 网络服务发现(Network Service Discovery)是一种在局域网内可以辨识并使用其他设备上提供的服务的技术,这种技术在端对端应用(例如:文件共享.联机游戏)中提供很好的帮助. NSD是基于Apple的Bonjo

【Android应用开发技术:用户界面】布局管理器

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:用户界面]章节列表 布局管理继承于ViewGroup.它用来管理Android应用用户界面里各组件,它的使用使得Android应用的图形用户界面具有良好的平台无关性. 常见的布局方式例如以下所看到的: 线性布局 表格布局 帧布局 相对布局 网络布

【Android应用开发技术:媒体开发】录像

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells 一 启用相机 1.1 请求相机权限 <manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /&g

【Android应用开发技术:媒体开发】音频

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells Android为播放音乐.闹铃.通知铃.来电声音.系统声音.打电话声音和DTMF频道都分别维护了一个隔离的音频流,这是我们能够控制不同音频的前提,这其中大多数的音频流都是被系统限制的,不能胡乱使用. 一 音频控制 默认情况下,按下音量控制键会调节当前被激活的音频流,如果我们的