AndroidPdfHelper——一个基于PdfRenderer的PDF加载库

前言

PDF文件是如今常见的文档格式之一,最近遇到一个加载网络PDF文件的需求,首先想到的是WebView的方式,想着IOS中自带的WebView支持加载PDF,想必Android应该也可以直接解析吧,然而调研一番发现


并没有类似的支持,只能自己动手丰衣足食了...

 

效果预览

目前Android中常见的加载PDF文件的方式有以下几种:

1.通过Google提供的文档服务加载

WebView本身并不能解析PDF文件,但是利用Google Docs提供的服务可以解析在线PDF链接:
只需要在原 url 前面拼上 http://docs.google.com/gview?url=,如下:

webview.loadurl("http://docs.google.com/gview?url=你的PDF链接");

但是使用这种方式的前提是要能连接上Google的服务,在国内的网络环境下有明显的局限性。

2.通过PDF.js结合WebView加载

PDF.js是Mozilla开源的一个PDF的阅读器,详情可参见 Mozilla/PDF.js
这个库所支持的功能也是比较全面的,但集成到项目中会导致包体增大5M,会有体积问题,当然,也可以采用服务器动态下发js文件加载的方式。

3.通过集成第三方库加载

目前已知的第三方库在功能和性能等指标可能存在较大的区别,集成原生第三方库的优点是体验好,但缺点是会显著的增加包大小,例如比较知名的 AndroidPdfViewer ,它是基于 PdfiumAndroid 的基础上进行封装,其so库文件也大幅度增加了包体的大小。

4.调起第三方支持PDF阅读的应用

由于系统本身的WebView不支持,可以采用应用外跳转的引导方式,将链接传递过去,使用其他支持的应用来加载:

File file = new File(getExternalCacheDir(),FILE_NAME);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(file);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "application/pdf");
startActivity(intent);

这种方式只能加载本地PDF文件,且要求设备上有支持阅读PDF文件的应用

5.通过原生PdfRenderer加载

Android本身也提供了加载PDF的 PDFRenderer,可以将PDF每一页转换为位图显示,但是只支持加载本地文件,所以加载在线文件需要先下载到本地再解析,另外,必须API>=21才能使用。
如果对文件的操作要求不高,可以考虑使用该方式,优点是几乎不影响包体的大小。

 
综合以上,决定基于PdfRenderer封装成一个PDF预览组件,项目地址:AndroidPdfHelper,满足基本的页面切换、手势放大缩小、自定义预览样式、快速导航等功能,最终效果如下:

pdf_preview_1.png

特性

1. 基于PdfRenderer实现,不同于其它第三方库,占用包体小
2. 支持PDF文件的上下页切换
3. 支持PDF单页的放大缩小查看
4. 支持设置文件预览清晰度
5. 支持自定义控制栏样式
6. 支持AndroidX

 

如何使用

在项目根目录的build.gradle添加:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

在项目的build.gradle添加如下依赖:

implementation 'com.github.GitHubZJY:AndroidPdfHelper:v1.0.0'

1)以View的方式引用(适用于需要自定义界面样式的场景)




在代码中初始化PdfView

PdfView pdfView = findViewById(R.id.pdf_view);

 
预览在线PDF文件:

pdfView.loadPdf("http://.....xx.pdf");

 
预览asset文件:

pdfView.loadPdf("file:///android_asset/test.pdf");

 

2)以页面方式调起(使用组件默认页面样式)

以页面的形式,自带了默认的顶部标题栏,适配Android 5.0以下,会自动下载并调用浏览器打开
预览在线PDF文件:

PdfPreviewUtils.previewPdf(context, "http://.....xx.pdf");

 
预览asset文件:

PdfPreviewUtils.previewPdf(context, "file:///android_asset/test.pdf");

 

3)设置预览清晰度




 
通过设置 quality 属性即可,目前一共有低、中、高三种清晰度,如下:

高清晰度:high
中等清晰度:medium
低清晰度:low

 

实现思路

PdfRenderer只支持加载本地PDF,如何加载网络文件?

由于PdfRenderer只支持加载本地PDF文件,如果要加载在线文件,可以先在后台开启下载任务,下载完成之后再调用PdfRenderer去解析。
 

PdfRenderer只支持API21以上,Android5.0以下如何适配?

由于PdfRenderer只支持Android5.0以上,所以判断当前版本,如果是5.0以下,则采用浏览器跳转方式。
 

如何解析PDF文件?

//存储位图数据集合
List pageList = new ArrayList();
PdfRenderer pdfRenderer = new PdfRenderer(getFileDescriptor());
int pageCount = pdfRenderer.getPageCount();
pageList.clear();
for (int i=0; i

通过 getPageCount 获取到PDF文件总的页数,然后再遍历调用 openPage 加载出每一页的 PdfRenderer.Page 对象,再通过其 render 方法,将页面转换为Bitmap对象。
注:这里的 qualityRatio 是用来计算最终的一个显示的比例,这个参数决定了生成的bitmap的尺寸,进而影响最终显示的清晰度,以及内存占用的大小。另外此处采用的是ARGB_4444的模式,避免内存占用过大。
 

如何实现分页切换?

上面的步骤已经将PDF文件的每一页转换为Bitmap对象并添加到集合中,可以利用这个Bitmap数据集合结合 RecyclerView 来进行跳转,每一个 RecyclerView 的Item就代表一页,滑动到某一页其实就是定位到RecyclerView的某个position,并且还可以根据position来获取当前所处的页标。

PdfPageAdapter pageAdapter = new PdfPageAdapter(getContext(), pageList);
recyclerView.setAdapter(pageAdapter);

例如滑动到下一页可以通过 LayoutManager 去控制:

currentIndex = pageLayoutManager.getCurrentPosition();
if(currentIndex + 1 < pageLayoutManager.getItemCount()) {
    currentIndex++;
    layoutManager.scrollToPosition(currentIndex);
}

 

如何实现手势放大缩小?

由于每一页PDF都会转换成一个 Bitmap,可以考虑采用 ImageView 承载显示,重写其 onTouch 方法监听触摸事件:

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            //设置拖动模式
            mMode = MODE_DRAG;
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            reSetMatrix();
            break;
        case MotionEvent.ACTION_MOVE:
            if (mMode == MODE_ZOOM) {
                setZoomMatrix(event);
            } else if (mMode == MODE_DRAG) {
                setDragMatrix(event);
            }
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            if (mMode == MODE_UNABLE) return true;
            //设置双击缩放模式
            mMode = MODE_ZOOM;
            break;
        default:
            break;
    }

    return mGestureDetector.onTouchEvent(event);
}

先判断出当前手势是单点拖动,还是双点缩放,再分别结合ImageView的 setImageMatrix 方法进行处理,从而达到对图片放大缩小及拖动的效果:

public void setDragMatrix(MotionEvent event) {
    float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
    float dy = event.getY() - startPoint.y; // 得到y轴的移动距离
    //避免和双击冲突,大于10f才算是拖动
    if (Math.sqrt(dx * dx + dy * dy) > 10f) {
        startPoint.set(event.getX(), event.getY());
        //在当前基础上移动
        mCurrentMatrix.set(getImageMatrix());
        float[] values = new float[9];
        mCurrentMatrix.getValues(values);
        dx = checkDxBound(values, dx);
        dy = checkDyBound(values, dy);
        //平移到新的坐标并设置新的矩阵
        mCurrentMatrix.postTranslate(dx, dy);
        setImageMatrix(mCurrentMatrix);
    }
}
private void setZoomMatrix(MotionEvent event) {
    //只有同时触屏两个点的时候才执行
    if (event.getPointerCount() < 2) return;
    float endDis = distance(event);// 结束距离
    if (endDis > 10f) {
        // 两个手指并拢在一起的时候像素大于10
        // 得到缩放倍数
        float scale = endDis / mStartDis;
        mStartDis = endDis;
        //在当前基础上伸缩
        mCurrentMatrix.set(getImageMatrix());
        float[] values = new float[9];
        mCurrentMatrix.getValues(values);

        scale = checkMaxScale(scale, values);
        setImageMatrix(mCurrentMatrix);
    }
}

 

结语

关于本库更多详细的用法可以查看README.md和源代码,目前支持在线或本地PDF文件预览,另外还支持侧边导航滑块,可快速滑动定位到任意一页。
由于PdfRenderer提供的支持有限,主要还是在于预览在线和本地PDF文件,但优点在于其体积小,后续会继续更新,后续会针对操作体验内存占用进一步优化,提供更多PDF预览方面的功能,欢迎issue和star~

 

欢迎关注 Android小Y 的,更多Android精选自定义View

『Android自定义View实战』实现一个小清新的弹出式圆环菜单
『Android自定义View实战』玩转PathMeasure之自定义支付结果动画
『Android自定义View实战』自定义弧形旋转菜单栏——卫星菜单
『Android自定义View实战』Android自定义带侧滑菜单的二维表格控件

GitHub:GitHubZJY
简 书:Android小Y
在GitHub上建了一个炫酷自定义View的集合ZJYWidget,主要是平时实现的一些实用的自定义View源码及demo,会长期维护,如有不足之处或建议还望指正,相互学习,相互进步~

你可能感兴趣的:(AndroidPdfHelper——一个基于PdfRenderer的PDF加载库)