标签:
作者:郭孝星
微博:郭孝星的新浪微博
邮箱:allenwells@163.com
博客:http://blog.csdn.net/allenwells
Github:https://github.com/AllenWells
在Android 4.4以及更高版本的系统中,提供了直接从Android应用程序打印图片和文字的服务,包括打印图片、HTML页面以及创建自定义打印文档等。
Android Support Library中的PrintHelper提供了一种打印图片的简单方法,该类有一个单一的布局选项:setScaleMode,它允许提供以下选项:
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的打印界面就会出现,允许用户选择一个打印机和它的打印机选项,用户可以选择打印图片和取消操作,当打印开始后,系统的通知栏里会显示一个打印通知提醒。
Android 4.4以后提供了WebView类,该类可以加载一个本地HTML资源或者从网上下载一个页面,然后创建打印任务,并把它交给Android打印服务。
WebView对象一般作为Activity布局的一部分,如果应用当前没有使用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页面加载完毕后,我们就可以创建打印任务了,如下所示:
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文档就不够了。这类应用的打印输出需要精确地控制每一个会在页面中显示的对象,包括字体,文本流,分页符,页眉,页脚和一些图像元素等等。
为了能够创建自定义打印文档,我们需要构建和打印框架可以相互通信的组件,调整打印参数,绘制页面元素并管理多个页面的打印。
连接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);
}
打印适配器负责与Android打印框架交互并处理打印过程的每一步。这个过程需要用户在创建打印文档前选择打印机和打印选项。由于用户可以选择不同性能的打印机,不同的页面尺寸或不同的页面方向,因此这些选项可能会影响最终的打印效果。当这些选项配置好之后,打印框架会寻求适配器进行布局并生成一个打印文档,以此作为打印的前期准备。一旦用户点击了打印按钮,框架会将最终的打印文档传递给一个打印提供程序(Print Provider)供打印输出。在打印过程中,用户可以选择取消打印,所以打印适配器必须监听并响应取消打印的请求。
PrintDocumentAdapter抽象类负责处理打印的生命周期,它的回调方法如下所示:
注意:这些适配器的回调方法会在应用的主线程上被调用。如果这些方法的实现在执行时可能需要花费大量的时间,那么我们可以把它们放到另一个线程里执行。例如:我们可以将布局或者写入打印文档的操作封装在一个AsyncTask对象中。
在实现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);
}
当将打印内容输出到一个文件是,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);
...
}
当应用进行打印时,我们需要生成一个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构建打印文档时,应该保证考虑到了哪些无法打印的边缘区域。
版权声明:当我们认真的去做一件事的时候,就能发现其中的无穷乐趣。作为程序员的我们,世界也更加简单,丰富多彩的技术宛如路上的风景,边走边欣赏。
标签:
原文地址:http://blog.csdn.net/allenwells/article/details/47294739