对于某些应用,例如绘图应用、页面布局应用以及专注于图形输出的其他应用,创建精美的打印页面是一项重要功能。在这种情况下,打印图片或 HTML 文档是不够的。对于此类应用,打印输出需要精确控制进入页面的所有内容,包括字体、文本流、分页符、页眉、页脚和图形元素。
比起之前讨论的方法,创建完全针对您的应用自定义的打印输出需要投入更多的编程工作。您必须构建与打印框架通信的组件,根据打印机设置做出调整,绘制页面元素以及管理多页打印。
本节课将向您介绍如何连接打印管理器、创建打印适配器和构建打印内容。
连接到打印管理器
当应用直接管理打印过程时,从用户那收到打印请求后的第一步是连接到 Android 打印框架并获取
Kotlin
private fun doPrint() {
activity?.also { context ->
// Get a PrintManager instance
val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager
// Set job name, which will be displayed in the print queue
val jobName = "${context.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, MyPrintDocumentAdapter(context), null)
}
}Java
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); //
}
以上示例代码演示了如何命名打印作业和设置处理打印生命周期步骤的
注意:
创建打印适配器
打印适配器与 Android 打印框架互动,并处理打印过程的步骤。此过程要求用户在创建打印文档前先选择打印机和打印选项。当用户选择具有不同输出功能、不同页面大小或不同页面方向的打印机时,这些选择会影响最终输出。在进行这些选择时,打印框架会要求您的适配器设置布局并生成打印文档,为最终输出做准备。当用户点按打印按钮后,框架将接受最终打印文档并将其传递给打印提供器以进行输出。在打印过程中,用户可以选择取消打印操作,因此打印适配器还必须监听和回应取消请求。
以下几部分介绍了如何实现布局和写入方法,这些方法对于打印适配器的正常运行至关重要。
注意:这些适配器方法是在应用的主线程上调用的。如果您预计在实现中执行这些方法要花费大量时间,则将它们实现为在单独的线程内执行。例如,您可以将布局或打印文档写入工作封装在单独的
计算打印文档信息
在
Kotlin
override fun onLayout(
oldAttributes: PrintAttributes?,
newAttributes: PrintAttributes,
cancellationSignal: CancellationSignal?,
callback: LayoutResultCallback,
extras: Bundle?
) {
// Create a new PdfDocument with the requested page attributes
pdfDocument = PrintedPdfDocument(activity, newAttributes)
// Respond to cancellation request
if (cancellationSignal?.isCanceled == true) {
callback.onLayoutCancelled()
return
}
// Compute the expected number of printed pages
val pages = computePageCount(newAttributes)
if (pages > 0) {
// Return print information to print framework
PrintDocumentInfo.Builder("print_output.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(pages)
.build()
.also { info ->
// Content layout reflow is complete
callback.onLayoutFinished(info, true)
}
} else {
// Otherwise report an error to the print framework
callback.onLayoutFailed("Page count calculation failed.")
}
}Java
@Override
public void onLayout(PrintAttributes oldAttributes,
PrintAttributes newAttributes,
CancellationSignal cancellationSignal,
LayoutResultCallback callback,
Bundle metadata) {
// Create a new PdfDocument with the requested page attributes
pdfDocument = new PrintedPdfDocument(getActivity(), newAttributes);
// Respond to cancellation request
if (cancellationSignal.isCanceled() ) {
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.");
}
}
执行
注意:
Kotlin
private fun computePageCount(printAttributes: PrintAttributes): Int {
var itemsPerPage = 4 // default item count for portrait mode
val pageSize = printAttributes.mediaSize
if (!pageSize.isPortrait) {
// Six items per page in landscape orientation
itemsPerPage = 6
}
// Determine number of print items
val printItemCount: Int = getPrintItemCount()
return Math.ceil((printItemCount / itemsPerPage.toDouble())).toInt()
}Java
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);
}
写入打印文档文件
到了要将打印输出写入文件时,Android 打印框架会调用应用的
注意:每次调用 false,以免不必要地重写打印文档。
注意:
以下示例演示了该过程的基本机制,并使用
Kotlin
override fun onWrite(
pageRanges: Array,
destination: ParcelFileDescriptor,
cancellationSignal: CancellationSignal?,
callback: WriteResultCallback
) {
// Iterate over each page of the document,
// check if it's in the output range.
for (i in 0 until totalPages) {
// 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?.startPage(i)?.also { page ->
// check for cancellation
if (cancellationSignal?.isCanceled == true) {
callback.onWriteCancelled()
pdfDocument?.close()
pdfDocument = null
return
}
// Draw page content for printing
drawPage(page)
// Rendering is complete, so page can be finalized.
pdfDocument?.finishPage(page)
}
}
}
// Write PDF document to file
try {
pdfDocument?.writeTo(FileOutputStream(destination.fileDescriptor))
} catch (e: IOException) {
callback.onWriteFailed(e.toString())
return
} finally {
pdfDocument?.close()
pdfDocument = null
}
val writtenPages = computeWrittenPages()
// Signal the print framework the document is complete
callback.onWriteFinished(writtenPages)
...
}Java
@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 = pdfDocument.startPage(i);
// check for cancellation
if (cancellationSignal.isCanceled()) {
callback.onWriteCancelled();
pdfDocument.close();
pdfDocument = null;
return;
}
// Draw page content for printing
drawPage(page);
// Rendering is complete, so page can be finalized.
pdfDocument.finishPage(page);
}
}
// Write PDF document to file
try {
pdfDocument.writeTo(new FileOutputStream(
destination.getFileDescriptor()));
} catch (IOException e) {
callback.onWriteFailed(e.toString());
return;
} finally {
pdfDocument.close();
pdfDocument = null;
}
PageRange[] writtenPages = computeWrittenPages();
// Signal the print framework the document is complete
callback.onWriteFinished(writtenPages);
...
}
此示例将 PDF 页面内容的呈现委托给 drawPage() 方法,详见下一部分的讨论。
和布局一样,执行
注意:呈现文档进行打印可能是一项消耗大量资源的操作。为了避免阻塞应用的主界面线程,应考虑在单独的线程上执行页面呈现和写入操作,例如在 进程和线程。
绘制 PDF 页面内容
当应用进行打印时,应用必须生成 PDF 文档并将其传递到 Android 打印框架进行打印。您可以将任何 PDF 生成库用于此目的。本节课介绍如何使用
Kotlin
private fun drawPage(page: PdfDocument.Page) {
page.canvas.apply {
// units are in points (1/72 of an inch)
val titleBaseLine = 72f
val leftMargin = 54f
val paint = Paint()
paint.color = Color.BLACK
paint.textSize = 36f
drawText("Test Title", leftMargin, titleBaseLine, paint)
paint.textSize = 11f
drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint)
paint.color = Color.BLUE
drawRect(100f, 100f, 172f, 172f, paint)
}
}Java
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);
}
使用
提示:虽然