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 自定义文档打印的整个流程,从项目准备、自定义打印适配器的创建和实现,到页面内容的绘制和打印,为实现更复杂的打印需求提供了基础。
超级会员免费看
3546

被折叠的 条评论
为什么被折叠?



