打印自定义文档
编写:jdneo - 原文:http://developer.android.com/training/printing/custom-docs.html
对于有些应用,比如绘图应用,页面布局应用和其它一些关注于图像输出的应用,创造出美丽的打印页面将是它的核心功能。在这种情况下,仅仅打印一幅图片或一个HTML文档就不够了。这类应用的打印输出需要精确地控制每一个会在页面中显示的对象,包括字体,文本流,分页符,页眉,页脚和一些图像元素等等。
想要创建一个完全自定义的打印文档,需要投入比之前讨论的方法更多的编程精力。你必须构建可以和打印框架相互通信的组件,调整打印参数,绘制页面元素并管理多个页面的打印。
这节课将向你展示如何连接打印管理器,创建一个打印适配器以及如何构建出需要打印的内容。
连接打印管理器
当你的应用直接管理打印进程时,在收到来自用户的打印请求后,第一步要做的是连接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";
// Start a print job, passing in a PrintDocumentAdapter implementation
// to handle the generation of a print document
printManager.print(jobName, new MyPrintDocumentAdapter(getActivity()),
null); //
}
上面的代码展示了如何命名一个打印任务以及如何设置一个PrintDocumentAdapter类的实例,它负责处理打印生命周期的每一步。打印适配器的实现会在下一节中进行讨论。
Note:[print()](http://developer.android.com/reference/android/print/PrintManager.html#print(java.lang.String, android.print.PrintDocumentAdapter, android.print.PrintAttributes))方法的最后一个参数接收一个PrintAttributes对象。你可以使用这个参数向打印框架进行一些打印设置,以及基于前一个打印周期的预设,从而改善用户体验。你也可以使用这个参数对打印内容进行一些更符合实际情况的设置,比如当打印一幅照片时,设置打印的方向与照片方向一致。
创建一个打印适配器
打印适配器负责与Android打印框架交互并处理打印过程的每一步。这个过程需要用户在创建打印文档前选择打印机和打印选项。由于用户可以选择不同性能的打印机,不同的页面尺寸或不同的页面方向,因此这些选项可能会影响最终的打印效果。当这些选项配置好之后,打印框架会寻求适配器进行布局并生成一个打印文档,以此作为打印的前期准备。一旦用户点击了打印按钮,框架会将最终的打印文档传递给一个打印提供程序(Print Provider)供打印输出。在打印过程中,用户可以选择取消打印,所以你的打印适配器必须监听并响应取消打印的请求。
PrintDocumentAdapter抽象类负责处理打印的生命周期,它有四个主要的回调方法。你必须在你的打印适配器中实现这些方法,以此来正确地和Android打印框架进行交互:
- onStart():一旦打印进程开始,该方法就将被调用。如果你的应用有任何一次性的准备任务要执行,比如获取一个要打印数据的快照,那么让它们在此处执行。在你的适配器中,这个回调方法不是必须实现的。
- [onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle)):每当用户改变了影响打印输出的设置时(比如改变了页面的尺寸,或者页面的方向)该函数将会被调用,以此给你的应用一个机会去重新计算打印页面的布局。另外,该方法必须返回打印文档包含多少页面。
- [onWrite()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onWrite(android.print.PageRange[], android.os.ParcelFileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback)):该方法调用后,会将打印页面渲染成一个待打印的文件。该方法可以在[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))方法被调用后调用一次或多次。
- onFinish():一旦打印进程结束后,该方法将会被调用。如果你的应用有任何一次性销毁任务要执行,让这些任务在该方法内执行。这个回调方法不是必须实现的。
下面将介绍如何实现onLayout()以及onWrite()方法,他们是打印适配器的核心功能。
Note:这些适配器的回调方法会在应用的主线程上被调用。如果这些方法的实现在执行时可能需要花费大量的时间,那么应该将他们放在另一个线程里执行。例如:你可以将布局或者写入打印文档的操作封装在一个AsyncTask对象中。
计算打印文档信息
在实现PrintDocumentAdapter类时,你的应用必须能够指定出所创建文档的类型,计算出打印任务所需要打印的总页数,并提供打印页面的尺寸信息。在实现适配器的[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))方法时,我们执行这些计算,并提供与理想的输出相关的一些信息,这些信息可以在PrintDocumentInfo类中获取,包括页数和内容类型。下面的例子展示了PrintDocumentAdapter中[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))方法的基本实现:
@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.");
}
}
[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))方法的执行结果有三种:完成,取消或失败(计算布局无法顺利完成时会失败)。你必须通过调用PrintDocumentAdapter.LayoutResultCallback对象中的适当方法来指出这些结果中的一个。
Note:[onLayoutFinished()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.LayoutResultCallback.html#onLayoutFinished(android.print.PrintDocumentInfo, boolean))方法的布尔类型参数明确了这个布局内容是否和上一次打印请求相比发生了改变。恰当地设定了这个参数将避免打印框架不必要的调用[onWrite()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onWrite(android.print.PageRange[], android.os.ParcelFileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback))方法,缓存之前的打印文档,提升执行性能。
[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))的主要任务是计算打印文档的页数,并将它作为打印参数交给打印机。如何计算页数则高度依赖于你的应用是如何对打印页面进行布局的。下面的代码展示了页数是如何根据打印方向确定的:
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打印框架会调用PrintDocumentAdapter类的[onWrite()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onWrite(android.print.PageRange[], android.os.ParcelFileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback))方法。这个方法的参数指定了哪些页面要被写入以及要使用的输出文件。该方法的实现必须将每一个请求页的内容渲染成一个含有多个页面的PDF文件。当这个过程结束以后,你需要调用callback对象的onWriteFinished()方法。
Note: Android打印框架可能会在每次调用[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))后,调用[onWrite()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onWrite(android.print.PageRange[], android.os.ParcelFileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback))方法一次甚至更多次。在这节课当中,有一件非常重要的事情是当打印内容的布局没有变化时,将[onLayoutFinished()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.LayoutResultCallback.html#onLayoutFinished(android.print.PrintDocumentInfo, boolean))方法的布尔参数设置为“false”,以此避免对打印文档进行不必要的重写操作。
Note:[onLayoutFinished()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.LayoutResultCallback.html#onLayoutFinished(android.print.PrintDocumentInfo, boolean))方法的布尔类型参数明确了这个布局内容是否和上一次打印请求相比发生了改变。恰当地设定了这个参数将避免打印框架不必要的调用[onLayout()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle))方法,缓存之前的打印文档,提升执行性能。
下面的代码展示了使用PrintedPdfDocument类创建了PDF文件的基本原理:
@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);
...
}
代码中将PDF页面递交给了drawPage()方法,这个方法会在下一部分介绍。
就布局而言,[onWrite()](http://developer.android.com/reference/android/print/PrintDocumentAdapter.html#onWrite(android.print.PageRange[], android.os.ParcelFileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback))方法的执行可以有三种结果:完成,取消或者失败(内容无法被写入)。你必须通过调用PrintDocumentAdapter.WriteResultCallback对象中的适当方法来指明这些结果中的一个。
Note:渲染打印文档是一个可能耗费大量资源的操作。为了避免阻塞应用的主UI线程,你应该考虑将页面的渲染和写操作放在另一个线程中执行,比如在AsyncTask中执行。关于更多异步任务线程的知识,可以阅读:Processes and Threads。
绘制PDF页面内容
当你的应用进行打印时,你的应用必须生成一个PDF文档并将它传递给Android打印框架以进行打印。你可以使用任何PDF生成库来协助完成这个操作。本节将展示如何使用PrintedPdfDocument类将你的打印内容生成为PDF页面。
PrintedPdfDocument类使用一个Canvas对象来在PDF页面上绘制元素,这一点和在activity布局上进行绘制很类似。你可以在打印页面上使用Canvas类提供的相关绘图方法绘制页面元素。下面的代码展示了如何使用这些方法在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页面上绘图时,元素通过单位“点(point)”来指定大小,一个点相当于七十二分之一英寸。确保你使用这个测量单位来指定页面上的元素大小。在定位绘制的元素时,坐标系的原点(即(0,0)点)在页面的最左上角。
Tip:虽然Canvas对象允许你将打印元素放置在一个PDF文档的边缘,但许多打印机无法在纸张的边缘打印。所以当你使用这个类构建一个打印文档时,确保你考虑了那些无法打印的边缘区域。
关于计算比例的,这个案例感觉不错
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.app;
import android.app.ListActivity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.pdf.PdfDocument.Page;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintManager;
import android.print.pdf.PrintedPdfDocument;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.android.apis.R;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* This class demonstrates how to implement custom printing support.
* <p>
* This activity shows the list of the MotoGP champions by year and
* brand. The print option in the overflow menu allows the user to
* print the content. The list list of items is laid out to such that
* it fits the options selected by the user from the UI such as page
* size. Hence, for different page sizes the printed content will have
* different page count.
* </p>
* <p>
* This sample demonstrates how to completely implement a {@link
* PrintDocumentAdapter} in which:
* <ul>
* <li>Layout based on the selected print options is performed.</li>
* <li>Layout work is performed only if print options change would change the content.</li>
* <li>Layout result is properly reported.</li>
* <li>Only requested pages are written.</li>
* <li>Write result is properly reported.</li>
* <li>Both Layout and write respond to cancellation.</li>
* <li>Layout and render of views is demonstrated.</li>
* </ul>
* </p>
*
* @see PrintManager
* @see PrintDocumentAdapter
*/
public class PrintCustomContent extends ListActivity {
private static final int MILS_IN_INCH = 1000;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new MotoGpStatAdapter(loadMotoGpStats(), getLayoutInflater()));
}
@Override public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.print_custom_content, menu);
return true;
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_print) {
print();
return true;
}
return super.onOptionsItemSelected(item);
}
private void print() {
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
printManager.print("MotoGP stats", new PrintDocumentAdapter() {
private int mRenderPageWidth;
private int mRenderPageHeight;
private PrintAttributes mPrintAttributes;
private PrintDocumentInfo mDocumentInfo;
private Context mPrintContext;
@Override public void onLayout(final PrintAttributes oldAttributes,
final PrintAttributes newAttributes, final CancellationSignal cancellationSignal,
final LayoutResultCallback callback, final Bundle metadata) {
// If we are already cancelled, don't do any work.
if (cancellationSignal.isCanceled()) {
callback.onLayoutCancelled();
return;
}
// Now we determined if the print attributes changed in a way that
// would change the layout and if so we will do a layout pass.
boolean layoutNeeded = false;
final int density = Math.max(newAttributes.getResolution().getHorizontalDpi(),
newAttributes.getResolution().getVerticalDpi());
// Note that we are using the PrintedPdfDocument class which creates
// a PDF generating canvas whose size is in points (1/72") not screen
// pixels. Hence, this canvas is pretty small compared to the screen.
// The recommended way is to layout the content in the desired size,
// in this case as large as the printer can do, and set a translation
// to the PDF canvas to shrink in. Note that PDF is a vector format
// and you will not lose data during the transformation.
// The content width is equal to the page width minus the margins times
// the horizontal printer density. This way we get the maximal number
// of pixels the printer can put horizontally.
final int marginLeft =
(int) (density * (float) newAttributes.getMinMargins().getLeftMils()
/ MILS_IN_INCH);
final int marginRight =
(int) (density * (float) newAttributes.getMinMargins().getRightMils()
/ MILS_IN_INCH);
final int contentWidth =
(int) (density * (float) newAttributes.getMediaSize().getWidthMils()
/ MILS_IN_INCH) - marginLeft - marginRight;
if (mRenderPageWidth != contentWidth) {
mRenderPageWidth = contentWidth;
layoutNeeded = true;
}
// The content height is equal to the page height minus the margins times
// the vertical printer resolution. This way we get the maximal number
// of pixels the printer can put vertically.
final int marginTop =
(int) (density * (float) newAttributes.getMinMargins().getTopMils()
/ MILS_IN_INCH);
final int marginBottom =
(int) (density * (float) newAttributes.getMinMargins().getBottomMils()
/ MILS_IN_INCH);
final int contentHeight =
(int) (density * (float) newAttributes.getMediaSize().getHeightMils()
/ MILS_IN_INCH) - marginTop - marginBottom;
if (mRenderPageHeight != contentHeight) {
mRenderPageHeight = contentHeight;
layoutNeeded = true;
}
// Create a context for resources at printer density. We will
// be inflating views to render them and would like them to use
// resources for a density the printer supports.
if (mPrintContext == null
|| mPrintContext.getResources().getConfiguration().densityDpi != density) {
Configuration configuration = new Configuration();
configuration.densityDpi = density;
mPrintContext = createConfigurationContext(configuration);
mPrintContext.setTheme(android.R.style.Theme_Holo_Light);
}
// If no layout is needed that we did a layout at least once and
// the document info is not null, also the second argument is false
// to notify the system that the content did not change. This is
// important as if the system has some pages and the content didn't
// change the system will ask, the application to write them again.
if (!layoutNeeded) {
callback.onLayoutFinished(mDocumentInfo, false);
return;
}
// For demonstration purposes we will do the layout off the main
// thread but for small content sizes like this one it is OK to do
// that on the main thread.
// Store the data as we will layout off the main thread.
final List<MotoGpStatItem> items =
((MotoGpStatAdapter) getListAdapter()).cloneItems();
new AsyncTask<Void, Void, PrintDocumentInfo>() {
@Override protected void onPreExecute() {
// First register for cancellation requests.
cancellationSignal.setOnCancelListener(new OnCancelListener() {
@Override public void onCancel() {
cancel(true);
}
});
// Stash the attributes as we will need them for rendering.
mPrintAttributes = newAttributes;
}
@Override protected PrintDocumentInfo doInBackground(Void... params) {
try {
// Create an adapter with the stats and an inflater
// to load resources for the printer density.
MotoGpStatAdapter adapter = new MotoGpStatAdapter(items,
(LayoutInflater) mPrintContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE));
int currentPage = 0;
int pageContentHeight = 0;
int viewType = -1;
View view = null;
LinearLayout dummyParent = new LinearLayout(mPrintContext);
dummyParent.setOrientation(LinearLayout.VERTICAL);
final int itemCount = adapter.getCount();
for (int i = 0; i < itemCount; i++) {
// Be nice and respond to cancellation.
if (isCancelled()) {
return null;
}
// Get the next view.
final int nextViewType = adapter.getItemViewType(i);
if (viewType == nextViewType) {
view = adapter.getView(i, view, dummyParent);
} else {
view = adapter.getView(i, null, dummyParent);
}
viewType = nextViewType;
// Measure the next view
measureView(view);
// Add the height but if the view crosses the page
// boundary we will put it to the next page.
pageContentHeight += view.getMeasuredHeight();
if (pageContentHeight > mRenderPageHeight) {
pageContentHeight = view.getMeasuredHeight();
currentPage++;
}
}
// Create a document info describing the result.
PrintDocumentInfo info =
new PrintDocumentInfo.Builder("MotoGP_stats.pdf").setContentType(
PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(currentPage + 1)
.build();
// We completed the layout as a result of print attributes
// change. Hence, if we are here the content changed for
// sure which is why we pass true as the second argument.
callback.onLayoutFinished(info, true);
return info;
} catch (Exception e) {
// An unexpected error, report that we failed and
// one may pass in a human readable localized text
// for what the error is if known.
callback.onLayoutFailed(null);
throw new RuntimeException(e);
}
}
@Override protected void onPostExecute(PrintDocumentInfo result) {
// Update the cached info to send it over if the next
// layout pass does not result in a content change.
mDocumentInfo = result;
}
@Override protected void onCancelled(PrintDocumentInfo result) {
// Task was cancelled, report that.
callback.onLayoutCancelled();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
@Override
public void onWrite(final PageRange[] pages, final ParcelFileDescriptor destination,
final CancellationSignal cancellationSignal, final WriteResultCallback callback) {
// If we are already cancelled, don't do any work.
if (cancellationSignal.isCanceled()) {
callback.onWriteCancelled();
return;
}
// Store the data as we will layout off the main thread.
final List<MotoGpStatItem> items =
((MotoGpStatAdapter) getListAdapter()).cloneItems();
new AsyncTask<Void, Void, Void>() {
private final SparseIntArray mWrittenPages = new SparseIntArray();
private final PrintedPdfDocument mPdfDocument =
new PrintedPdfDocument(PrintCustomContent.this, mPrintAttributes);
@Override protected void onPreExecute() {
// First register for cancellation requests.
cancellationSignal.setOnCancelListener(new OnCancelListener() {
@Override public void onCancel() {
cancel(true);
}
});
}
@Override protected Void doInBackground(Void... params) {
// Go over all the pages and write only the requested ones.
// Create an adapter with the stats and an inflater
// to load resources for the printer density.
MotoGpStatAdapter adapter = new MotoGpStatAdapter(items,
(LayoutInflater) mPrintContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE));
int currentPage = -1;
int pageContentHeight = 0;
int viewType = -1;
View view = null;
Page page = null;
LinearLayout dummyParent = new LinearLayout(mPrintContext);
dummyParent.setOrientation(LinearLayout.VERTICAL);
// The content is laid out and rendered in screen pixels with
// the width and height of the paper size times the print
// density but the PDF canvas size is in points which are 1/72",
// so we will scale down the content.
final float scale = Math.min(
(float) mPdfDocument.getPageContentRect().width() / mRenderPageWidth,
(float) mPdfDocument.getPageContentRect().height() / mRenderPageHeight);
final int itemCount = adapter.getCount();
for (int i = 0; i < itemCount; i++) {
// Be nice and respond to cancellation.
if (isCancelled()) {
return null;
}
// Get the next view.
final int nextViewType = adapter.getItemViewType(i);
if (viewType == nextViewType) {
view = adapter.getView(i, view, dummyParent);
} else {
view = adapter.getView(i, null, dummyParent);
}
viewType = nextViewType;
// Measure the next view
measureView(view);
// Add the height but if the view crosses the page
// boundary we will put it to the next one.
pageContentHeight += view.getMeasuredHeight();
if (currentPage < 0 || pageContentHeight > mRenderPageHeight) {
pageContentHeight = view.getMeasuredHeight();
currentPage++;
// Done with the current page - finish it.
if (page != null) {
mPdfDocument.finishPage(page);
}
// If the page is requested, render it.
if (containsPage(pages, currentPage)) {
page = mPdfDocument.startPage(currentPage);
page.getCanvas().scale(scale, scale);
// Keep track which pages are written.
mWrittenPages.append(mWrittenPages.size(), currentPage);
} else {
page = null;
}
}
// If the current view is on a requested page, render it.
if (page != null) {
// Layout an render the content.
view.layout(0, 0, view.getMeasuredWidth(),
view.getMeasuredHeight());
view.draw(page.getCanvas());
// Move the canvas for the next view.
page.getCanvas().translate(0, view.getHeight());
}
}
// Done with the last page.
if (page != null) {
mPdfDocument.finishPage(page);
}
// Write the data and return success or failure.
try {
mPdfDocument.writeTo(
new FileOutputStream(destination.getFileDescriptor()));
// Compute which page ranges were written based on
// the bookkeeping we maintained.
PageRange[] pageRanges = computeWrittenPageRanges(mWrittenPages);
callback.onWriteFinished(pageRanges);
} catch (IOException ioe) {
callback.onWriteFailed(null);
} finally {
mPdfDocument.close();
}
return null;
}
@Override protected void onCancelled(Void result) {
// Task was cancelled, report that.
callback.onWriteCancelled();
mPdfDocument.close();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
private void measureView(View view) {
final int widthMeasureSpec = ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(mRenderPageWidth, MeasureSpec.EXACTLY), 0,
view.getLayoutParams().width);
final int heightMeasureSpec = ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(mRenderPageHeight, MeasureSpec.EXACTLY), 0,
view.getLayoutParams().height);
view.measure(widthMeasureSpec, heightMeasureSpec);
}
private PageRange[] computeWrittenPageRanges(SparseIntArray writtenPages) {
List<PageRange> pageRanges = new ArrayList<PageRange>();
int start = -1;
int end = -1;
final int writtenPageCount = writtenPages.size();
for (int i = 0; i < writtenPageCount; i++) {
if (start < 0) {
start = writtenPages.valueAt(i);
}
int oldEnd = end = start;
while (i < writtenPageCount && (end - oldEnd) <= 1) {
oldEnd = end;
end = writtenPages.valueAt(i);
i++;
}
PageRange pageRange = new PageRange(start, end);
pageRanges.add(pageRange);
start = end = -1;
}
PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
pageRanges.toArray(pageRangesArray);
return pageRangesArray;
}
private boolean containsPage(PageRange[] pageRanges, int page) {
final int pageRangeCount = pageRanges.length;
for (int i = 0; i < pageRangeCount; i++) {
if (pageRanges[i].getStart() <= page && pageRanges[i].getEnd() >= page) {
return true;
}
}
return false;
}
}, null);
}
private List<MotoGpStatItem> loadMotoGpStats() {
String[] years = getResources().getStringArray(R.array.motogp_years);
String[] champions = getResources().getStringArray(R.array.motogp_champions);
String[] constructors = getResources().getStringArray(R.array.motogp_constructors);
List<MotoGpStatItem> items = new ArrayList<MotoGpStatItem>();
final int itemCount = years.length;
for (int i = 0; i < itemCount; i++) {
MotoGpStatItem item = new MotoGpStatItem();
item.year = years[i];
item.champion = champions[i];
item.constructor = constructors[i];
items.add(item);
}
return items;
}
private static final class MotoGpStatItem {
String year;
String champion;
String constructor;
}
private class MotoGpStatAdapter extends BaseAdapter {
private final List<MotoGpStatItem> mItems;
private final LayoutInflater mInflater;
public MotoGpStatAdapter(List<MotoGpStatItem> items, LayoutInflater inflater) {
mItems = items;
mInflater = inflater;
}
public List<MotoGpStatItem> cloneItems() {
return new ArrayList<MotoGpStatItem>(mItems);
}
@Override public int getCount() {
return mItems.size();
}
@Override public Object getItem(int position) {
return mItems.get(position);
}
@Override public long getItemId(int position) {
return position;
}
@Override public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.motogp_stat_item, parent, false);
}
MotoGpStatItem item = (MotoGpStatItem) getItem(position);
TextView yearView = (TextView) convertView.findViewById(R.id.year);
yearView.setText(item.year);
TextView championView = (TextView) convertView.findViewById(R.id.champion);
championView.setText(item.champion);
TextView constructorView = (TextView) convertView.findViewById(R.id.constructor);
constructorView.setText(item.constructor);
return convertView;
}
}
}
网友评论