需求描述:将WebView渲染出来的内容转化为PDF文件打印
关于内容转为文件打印,google一下,文章还是有很多的,这里简单的贴一下
- app/build.gradle 添加
implementation "com.linkedin.dexmaker:dexmaker:2.28.1"
implementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
- 实现类
public class H52PdfTask {
ParcelFileDescriptor descriptor;
PageRange[] ranges;
PrintDocumentAdapter printAdapter;
public void webViewToPdf (WebView webView, String pdfFilePath) {
try {
File pdfFile = new File(pdfFilePath);
if (pdfFile.exists()) {
pdfFile.delete();
}
pdfFile.createNewFile();
descriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_WRITE);
// 设置打印参数
PrintAttributes.MediaSize isoA4 = PrintAttributes.MediaSize.ISO_A4;
PrintAttributes attributes = new PrintAttributes.Builder()
.setMediaSize(isoA4)
.setResolution(new PrintAttributes.Resolution("id", Context.PRINT_SERVICE, 240, 240))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build();
// 计算webview打印需要的页数
int numberOfPages = ((webView.getContentHeight() * 240 / (isoA4.getHeightMils())) );
ranges = new PageRange[]{new PageRange(0, numberOfPages)};
// 创建pdf文件缓存目录
// 获取需要打印的webview适配器
printAdapter = webView.createPrintDocumentAdapter();
// 开始打印
printAdapter.onStart();
printAdapter.onLayout(attributes, attributes, new CancellationSignal(),
getLayoutResultCallback((proxy, method, args) -> {
if (method.getName().equals("onLayoutFinished")) {
L.i("H52PdfTask onLayoutFinished thread=" + Thread.currentThread().getName());
// 监听到内部调用了onLayoutFinished()方法,即打印成功
onLayoutSuccess();
} else {
// 监听到打印失败或者取消了打印
L.i("H52PdfTask onLayout fail");
}
return null;
}), new Bundle());
} catch (Exception e) {
e.printStackTrace();
}
}
private void onLayoutSuccess () throws IOException {
PrintDocumentAdapter.WriteResultCallback callback = getWriteResultCallback(new InvocationHandler() {
@Override
public Object invoke (Object o, Method method, Object[] objects) {
if (method.getName().equals("onWriteFinished")) {
L.i("H52PdfTask onLayoutSuccess onWriteFinished thread=" + Thread.currentThread().getName());
} else {
L.i("H52PdfTask onLayoutSuccess fail");
}
return null;
}
});
printAdapter.onWrite(ranges, descriptor, new CancellationSignal(), callback);
}
public static PrintDocumentAdapter.LayoutResultCallback getLayoutResultCallback (InvocationHandler invocationHandler) throws IOException {
return ProxyBuilder.forClass(PrintDocumentAdapter.LayoutResultCallback.class)
.handler(invocationHandler)
.build();
}
public static PrintDocumentAdapter.WriteResultCallback getWriteResultCallback (InvocationHandler invocationHandler) throws IOException {
return ProxyBuilder.forClass(PrintDocumentAdapter.WriteResultCallback.class)
.handler(invocationHandler)
.build();
}
}
!!!遇到的问题
大家都知道,WebView加载Url时,其中的图片是以懒加载的方式即可见加载,问题也就随之出来了,当我们没有浏览完整个网页时,打印出来的文档,没有加载完成的图片并不能显示出来,可能是个菊花,可能是个灰底的未加载的图。
怎么才能让WebView一次性将整个网页的图片全部加载出来呢,搜索了国内外的文章,发现更多的都是阻塞图片加载让网页更快速的显示出来,并没有这种反人类需求的解答(也可能是我的搜索引擎不强),又去看官网WebView相关的api,也没有收获(也可能是我英文阅读能力不强)。
就在一筹莫展之际,发现WebView可以被解析成长截屏( webView.capturePicture()
),写个demo试下(WebView加载完成之后,点个Button,把生成的长截屏解析成bitmap设置到ImageView上),先改个布局
<ScrollView>
<androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatButton .../>
<androidx.appcompat.widget.AppCompatImageView .../>
<WebView ... />
<androidx.appcompat.widget.LinearLayoutCompat>
</ScrollView>
跑完demo测试发现,长截屏的方式真的可以,而且更神奇的是,WebView不可见时(并没有上滑WebView使图片加载)的图片竟然也在长截屏上正常的显示出来了,这.......这些不可见时的图片,是t(爱)m(你)什么时候被加载的,合着调了个截屏的方法就加载了??????
说是迟那时快,忽然提壶灌顶,改了下布局
<ScrollView>
<androidx.appcompat.widget.LinearLayoutCompat>
<WebView ... />
<androidx.appcompat.widget.LinearLayoutCompat>
</ScrollView>
run,我艹,成了,没问题了,可以了,好起来了,其中全部的图片都加载了。
具体的原理大家自己搜索下,这里就不贴链接了
- ScrollView高度测试原理
- WebView高度计算
关于ScrollView嵌套WebView的滑动冲突问题,那才值几个钱,呼呼就解决。
注:
ScrollView单嵌套WebView并没有发现冲突(测试机型:华为,红米,小米),可能是我加载的网页比较简单。
其实还有其他实现思路,还没来得及尝试就解决了,你说气不气
- 2个WebView,前台WebView加载与用户交互,后台WebView自动缓慢滑动至底部,对于打印的操作直接操作后台的WebView就好了,不滑动到底部不允许打印!!!
- 1个WebView,不滑动到底部不允许打印!!!
Ending
如果各位大佬有更好的方法,还请评论区赐教。
网友评论