52、Android 自定义文档打印指南

Android 自定义文档打印指南

1. Android HTML 和网页内容打印示例

在 Android 开发中,若要实现 HTML 和网页内容的打印,可在依赖项中添加如下配置:

dependencies {
    // ...
    implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
    // ...
}

Android 打印框架对 WebView 类进行了扩展,使得在 Android 应用程序中能够打印基于 HTML 的内容。这些内容可以是应用程序在运行时动态创建的 HTML,也可以是加载到 WebView 实例中的现有网页。对于动态创建的 HTML,务必使用 WebViewClient 实例,以确保在 HTML 完全加载到 WebView 后再开始打印。

2. Android 自定义文档打印概述

当内容为图像或 HTML 标记形式时,Android 打印框架能较轻松地为应用程序添加打印支持。对于更高级的打印需求,则可利用打印框架的自定义文档打印功能。

自定义文档打印的基本原理是使用画布来表示要打印的文档页面。应用程序将待打印的内容以形状、颜色、文本和图像的形式绘制到这些画布上,这些画布由 Android Canvas 类的实例表示,提供了丰富的绘图选项。完成所有页面的绘制后,即可打印文档。

要实现自定义文档打印,需完成以下步骤:
1. 实现一个继承自 PrintDocumentAdapter 类的自定义打印适配器。
2. 获取打印管理器服务的引用。
3. 创建 PdfDocument 类的实例来存储文档页面。
4. 以 PdfDocument.Page 实例的形式向 PdfDocument 添加页面。
5. 获取与文档页面关联的 Canvas 对象的引用。
6. 在画布上绘制内容。
7. 将 PDF 文档写入打印框架提供的目标输出流。
8. 通知打印框架文档已准备好打印。

3. 自定义打印适配器

打印适配器的作用是为打印框架提供要打印的内容,并确保其格式符合用户的选择偏好(如纸张尺寸和页面方向)。

在打印 HTML 和图像时,Android 打印框架提供的打印适配器会完成大部分工作。例如,打印网页时,调用 WebView 类实例的 createPrintDocumentAdapter() 方法会为我们创建一个打印适配器。

而在自定义文档打印中,应用程序开发者需自行设计打印适配器,并实现绘制和格式化内容的代码,为打印做准备。

自定义打印适配器通过继承 PrintDocumentAdapter 类并重写其中的一组回调方法来创建,这些回调方法会在打印过程的不同阶段被打印框架调用,具体如下:
| 回调方法 | 说明 | 是否必须实现 |
| ---- | ---- | ---- |
| onStart() | 打印过程开始时调用,用于执行创建打印作业所需的任何必要任务 | 可选 |
| onLayout() | 在调用 onStart() 方法后调用,并且每次用户更改打印设置(如更改方向、纸张尺寸或颜色设置)时都会再次调用。该方法应调整内容和布局以适应这些更改,并返回要打印的页面数 | 必须 |
| onWrite() | 在每次调用 onLayout() 方法后调用,负责在要打印的页面画布上渲染内容。渲染完成后,需将生成的 PDF 文档写入提供的文件描述符。然后调用 onWriteFinished() 回调方法,并传递包含要打印页面范围信息的参数 | 必须 |
| onFinish() | 打印过程完成时,打印框架会调用该方法(可选),为应用程序提供执行必要清理操作的机会 | 可选 |

以下是自定义打印适配器的简化流程图:

graph TD;
    A[打印开始] --> B[onStart()];
    B --> C[onLayout()];
    C --> D{用户更改设置?};
    D -- 是 --> C;
    D -- 否 --> E[onWrite()];
    E --> F[绘制内容];
    F --> G[写入 PDF];
    G --> H[onWriteFinished()];
    H --> I[onFinish()];
4. 准备自定义文档打印项目

若要开始自定义文档打印项目,可按以下步骤操作:
1. 从欢迎屏幕中选择“新建项目”选项。
2. 在弹出的新项目对话框中,选择“空视图活动”模板,然后点击“下一步”按钮。
3. 在“名称”字段中输入 CustomPrint ,并指定 com.ebookfrenzy.customprint 作为包名。
4. 在点击“完成”按钮之前,将“最低 API 级别”设置为 API 26:Android 8.0(Oreo),并将“语言”菜单选择为 Java。
5. 将 activity_main.xml 布局文件加载到布局编辑器工具中,在设计模式下选择并删除“Hello World!” TextView 对象。
6. 从调色板的“常用”部分拖放一个 Button 视图,并将其放置在布局视图的中心。
7. 选中 Button 视图,将文本属性更改为“Print Document”,并将该字符串提取到新资源中。完成后,用户界面布局应与预期一致。
8. 当应用程序中选择该按钮时,需要调用一个方法来启动文档打印过程。在属性工具窗口中,将 onClick 属性设置为调用名为 printDocument 的方法。

5. 创建自定义打印适配器

在 Android 应用程序中打印自定义文档的大部分工作在于实现自定义打印适配器。此示例需要一个实现了 onLayout() onWrite() 回调方法的打印适配器。在 MainActivity.java 文件中,添加新类的模板如下:

package com.ebookfrenzy.customprint;
// ...
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.content.Context;

public class MainActivity extends AppCompatActivity {

    public static class MyPrintDocumentAdapter extends PrintDocumentAdapter
    {
        Context context;

        MyPrintDocumentAdapter(Context context)
        {
            this.context = context;
        }

        @Override
        public void onLayout(PrintAttributes oldAttributes,
                             PrintAttributes newAttributes,
                             CancellationSignal cancellationSignal,
                             LayoutResultCallback callback,
                             Bundle metadata) {
        }

        @Override
        public void onWrite(final PageRange[] pageRanges,
                            final ParcelFileDescriptor destination,
                            final CancellationSignal
                                    cancellationSignal,
                            final WriteResultCallback callback) {
        }
    }
    // ...
}

当前新类包含一个构造方法,创建新类实例时会调用该方法。构造方法接受调用活动的上下文作为参数,并将其存储起来,以便在两个回调方法中引用。

6. 实现 onLayout() 回调方法

MainActivity.java 文件中,首先添加 onLayout() 方法所需的导入指令:

package com.ebookfrenzy.customprint;
// ...
import android.print.PrintDocumentInfo;
import android.print.pdf.PrintedPdfDocument;
import android.graphics.pdf.PdfDocument;

public class MainActivity extends AppCompatActivity {
    // ...
}

接着,修改 MyPrintDocumentAdapter 类,声明 onLayout() 方法中要使用的变量:

public static class MyPrintDocumentAdapter extends PrintDocumentAdapter
{
    Context context;
    int pageHeight;
    int pageWidth;
    PdfDocument myPdfDocument; 
    int totalpages = 4;
    // ...
}

在本示例中,将打印一个四页的文档。在更复杂的情况下,应用程序可能需要根据内容的数量和布局,结合用户选择的纸张尺寸和页面方向,动态计算要打印的页面数。

实现 onLayout() 方法的代码如下:

@Override
public void onLayout(PrintAttributes oldAttributes,
                     PrintAttributes newAttributes,
                     CancellationSignal cancellationSignal,
                     LayoutResultCallback callback,
                     Bundle metadata) {

    myPdfDocument = new PrintedPdfDocument(context, newAttributes);

    pageHeight = 
        Objects.requireNonNull(
            newAttributes.getMediaSize()).getHeightMils()/1000 * 72;
    pageWidth = 
        newAttributes.getMediaSize().getWidthMils()/1000 * 72;

    if (cancellationSignal.isCanceled() ) {
        callback.onLayoutCancelled();
        return;
    }

    if (totalpages > 0) {
        PrintDocumentInfo.Builder builder = new PrintDocumentInfo
           .Builder("print_output.pdf").setContentType(
                PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
           .setPageCount(totalpages);

        PrintDocumentInfo info = builder.build();
        callback.onLayoutFinished(info, true);
    } else {
        callback.onLayoutFailed("Page count is zero.");
    }
}

此方法完成了多项任务:
- 创建一个新的 PDF 文档,作为 PdfDocument 类的实例。打印框架调用 onLayout() 方法时传递的参数中,有一个 PrintAttributes 类型的对象,包含用户为打印输出选择的纸张尺寸、分辨率和颜色设置等详细信息。创建 PDF 文档时会使用这些设置,同时还会使用构造方法中存储的活动上下文。
- 使用 PrintAttributes 对象提取文档页面的高度和宽度值。这些尺寸以千分之一英寸为单位存储在对象中,由于后续方法使用的单位是 1/72 英寸,因此在存储前需要进行转换。
- 若用户取消打印过程, onLayout() 方法会检测到并调用 LayoutResultCallback 对象的 onLayoutCancelled() 方法,通知打印框架取消请求已收到,布局任务已取消。
- 布局工作完成后,方法需调用 LayoutResultCallback 对象的 onLayoutFinished() 方法,传递两个参数:一个包含要打印文档信息的 PrintDocumentInfo 对象,以及一个布尔值,指示自上次调用 onLayout() 方法以来布局是否发生了变化。若页面数为零,则调用 onLayoutFailed() 方法向打印框架报告失败。

调用 onLayoutFinished() 方法会通知打印框架布局工作已完成,从而触发对 onWrite() 方法的调用。

Android 自定义文档打印指南

7. 实现 onWrite() 回调方法

onWrite() 回调方法负责渲染文档的页面,并通知打印框架文档已准备好打印。完成后的 onWrite() 方法代码如下:

package com.ebookfrenzy.customprint;

import java.io.FileOutputStream;
import java.io.IOException;
// ...
import android.graphics.pdf.PdfDocument.PageInfo;
// ...
@Override
public void onWrite(final PageRange[] pageRanges,
                    final ParcelFileDescriptor destination,
                    final CancellationSignal cancellationSignal,
                    final WriteResultCallback callback) {

    for (int i = 0; i < totalpages; i++) {
        if (pageInRange(pageRanges, i))
        {
            PageInfo newPage = new PageInfo.Builder(pageWidth, 
                 pageHeight, i).create();

            PdfDocument.Page page = 
                myPdfDocument.startPage(newPage);

            if (cancellationSignal.isCanceled()) {
                callback.onWriteCancelled();
                myPdfDocument.close();
                myPdfDocument = null;
                return;
            }
            drawPage(page, i);
            myPdfDocument.finishPage(page);  
        }
    }

    try {
        myPdfDocument.writeTo(new FileOutputStream(
                    destination.getFileDescriptor()));
    } catch (IOException e) {
        callback.onWriteFailed(e.toString());
        return;
    } finally {
        myPdfDocument.close();
        myPdfDocument = null;
    }

    callback.onWriteFinished(pageRanges);
}

onWrite() 方法的执行流程如下:
1. 遍历文档中的每一页。但要考虑用户可能只请求打印文档中的部分页面,打印框架会传递一个 PageRange 对象数组,指示要打印的页面范围。
2. 对于在指定范围内的每一页,创建一个新的 PdfDocument.Page 对象,使用 onLayout() 方法中存储的高度和宽度值,确保页面大小与用户选择的打印选项匹配。
3. 响应取消请求,若检测到取消信号,调用 WriteResultCallback 对象的 onWriteCancelled() 方法,关闭并释放 myPdfDocument 变量。
4. 调用 drawPage() 方法绘制当前页面的内容,然后调用 myPdfDocument.finishPage() 方法完成该页面。
5. 将 PDF 文档写入目标输出流,若写入过程中出现 IOException ,调用 onWriteFailed() 方法通知打印框架写入失败。
6. 最后,调用 WriteResultCallback 对象的 onWriteFinished() 方法,通知打印框架文档已准备好打印。

以下是 onWrite() 方法的简化流程图:

graph TD;
    A[开始 onWrite()] --> B[遍历页面];
    B --> C{页面在范围内?};
    C -- 是 --> D[创建新页面];
    D --> E{是否取消?};
    E -- 是 --> F[取消写入];
    E -- 否 --> G[绘制页面];
    G --> H[完成页面];
    C -- 否 --> B;
    H --> I[写入 PDF];
    I --> J{写入成功?};
    J -- 是 --> K[完成写入];
    J -- 否 --> L[写入失败];
8. 检查页面是否在范围内

onWrite() 方法被调用时,会传递一个 PageRange 对象数组,指示文档中要打印的页面范围。 PageRange 类用于存储页面范围的起始和结束页面,可通过其 getStart() getEnd() 方法访问。

MyPrintDocumentAdapter 类中实现 pageInRange() 方法,用于判断指定页面号是否在指定范围内,代码如下:

public class MyPrintDocumentAdapter extends PrintDocumentAdapter {
    // ...
    private boolean pageInRange(PageRange[] pageRanges, int page)
    {
        for (PageRange pageRange : pageRanges) {
            if ((page >= pageRange.getStart()) &&
                    (page <= pageRange.getEnd()))
                return true;
        }
        return false;
    }
    // ...
}
9. 在页面画布上绘制内容

现在需要编写代码在页面上绘制内容,以便进行打印。绘制的内容完全取决于应用程序的需求,仅受 Android Canvas 类功能的限制。在本示例中,将在画布上绘制一些简单的文本和图形。

MainActivity.java 文件中实现 drawPage() 方法,代码如下:

package com.ebookfrenzy.customprint;
// ...
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

public class MainActivity extends AppCompatActivity {
    // ...
    public static class MyPrintDocumentAdapter extends 
                                PrintDocumentAdapter
    {
        // ...
        private void drawPage(PdfDocument.Page page, 
                        int pagenumber) {
            Canvas canvas = page.getCanvas();

            pagenumber++; // 确保页码从 1 开始

            int titleBaseLine = 72;
            int leftMargin = 54;

            Paint paint = new Paint();
            paint.setColor(Color.BLACK);
            paint.setTextSize(40);
            canvas.drawText(
               "Test Print Document Page " + pagenumber,
                                               leftMargin,
                                               titleBaseLine, 
                                               paint);

            paint.setTextSize(14);
            canvas.drawText("This is some test content to verify that custom document printing works", leftMargin, titleBaseLine + 35, paint);

            if (pagenumber % 2 == 0)
                paint.setColor(Color.RED);
            else
                paint.setColor(Color.GREEN);

            PageInfo pageInfo = page.getInfo();


            canvas.drawCircle(pageInfo.getPageWidth()/2f,
                               pageInfo.getPageHeight()/2f, 
                               150, 
                               paint); 
        }
        // ...
    }

drawPage() 方法的执行步骤如下:
1. 由于代码中的页码从 0 开始,而文档通常从第 1 页开始,因此先将页码加 1。
2. 获取与页面关联的 Canvas 对象,声明一些边距和基线值。
3. 创建 Paint Color 对象,设置文本大小,绘制页面标题文本,包括当前页码。
4. 根据页码的奇偶性设置不同的颜色,在页面中心绘制一个圆形。

通过以上步骤,我们完成了 Android 自定义文档打印的整个流程,从项目准备、自定义打印适配器的创建和实现,到页面内容的绘制和打印,为实现更复杂的打印需求提供了基础。

分布式微服务企业级系统是一个基于Spring、SpringMVC、MyBatis和Dubbo等技术的分布式敏捷开发系统架构。该系统采用微服务架构和模块化设计,提供整套公共微服务模块,包括集中权限管理(支持单点登录)、内容管理、支付中心、用户管理(支持第三方登录)、微信平台、存储系统、配置中心、日志分析、任务和通知等功能。系统支持服务治理、监控和追踪,确保高可用性和可扩展性,适用于中小型企业的J2EE企业级开发解决方案。 该系统使用Java作为主要编程语言,结合Spring框架实现依赖注入和事务管理,SpringMVC处理Web请求,MyBatis进行数据持久化操作,Dubbo实现分布式服务调用。架构模式包括微服务架构、分布式系统架构和模块化架构,设计模式应用了单例模式、工厂模式和观察者模式,以提高代码复用性和系统稳定性。 应用场景广泛,可用于企业信息化管理、电子商务平台、社交应用开发等领域,帮助开发者快速构建高效、安全的分布式系统。本资源包含完整的源码和详细论文,适合计算机科学或软件工程专业的毕业设计参考,提供实践案例和技术文档,助力学生和开发者深入理解微服务架构和分布式系统实现。 【版权说明】源码来源于网络,遵循原项目开源协议。付费内容为本人原创论文,包含技术分析和实现思路。仅供学习交流使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值