Android文档阅读之PDF文档阅读的方案实现
上一节说到了Office文档的阅读实现方案,通过NoHTTP将文档下载到本地,然后策略阅读,如果还没阅读到上一篇的小伙伴们,可以先移步到上一节,因为这篇包括后面所写的都会用到这个NoHttp。
打开PDF,我们需要引入第三方的PDF解析框架,Android studio直接引入:
compile 'es.voghdev.pdfviewpager:library:1.0.3'
该库提供一个PDFPagerAdapter类,实际上它可以充当一个ViewPager的Adapter,所以我们在使用之前,先自己写一个ViewPager类,这是为了解决PDF在缩放滑动时产生的天然的Bug IllegalArgumentException,该异常是ViewPager内部报出来的,在我们无法自己手动写一个ViewPager的时候,我们这时候最好办法就是继承一个ViewPager并且捕获该异常,具体如下:
PDFViewPager.java
import android.content.Context;
import android.os.Handler;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import java.io.File;
import es.voghdev.pdfviewpager.library.RemotePDFViewPager;
import es.voghdev.pdfviewpager.library.remote.DownloadFile;
import es.voghdev.pdfviewpager.library.remote.DownloadFileUrlConnectionImpl;
import es.voghdev.pdfviewpager.library.util.FileUtil;
/**
* @Description: 继承第三方PDF控件,
* 重写onTouchEvent()、onInterceptTouchEvent(),处理滑动越界问题
* @Author: zzj
* @Date: 2018/9/6 18:48
* @Version: 1.0.0
*/
public class PDFViewPager extends ViewPager {
private Context mContext;
protected DownloadFile.Listener listener;
public PDFViewPager(Context context) {
super(context);
this.mContext = context;
}
public PDFViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
return super.onTouchEvent(event);
} catch (IllegalArgumentException e) {
//捕获,不做处理,避免崩溃
Log.e("zzj", "PDFViewPager onTouchEvent() IllegalArgumentException");
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
try {
return super.onInterceptTouchEvent(event);
} catch (IllegalArgumentException e) {
//捕获,不做处理,避免崩溃
Log.e("zzj", "PDFViewPager onInterceptTouchEvent() IllegalArgumentException");
}
return false;
}
}
我们通过重写ViewPager的onTouchEvent()方法和onInterceptTouchEvent()方法捕获IllegalArgumentException异常,避免在PDF滑动缩放时报错。
我们先上activity的布局act_reader_pdf.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rl_pdf_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.reader.PDFViewPager
android:id="@+id/pdf_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_centerInParent="true"
android:progress="10"
android:progressDrawable="@drawable/progress_bar_vip_center"
android:visibility="gone" />
<include
layout="@layout/layout_data_download"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="50dp"
android:visibility="gone" />
</RelativeLayout>
ProgressBar为一个打开进度条,当我们的PDF文件很大时,这一点就会比较友好,会相应提升进度。ProgressBar的样式progress_bar_vip_center.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="1dp"/>
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="1dp"/>
<solid android:color="#FFCE00" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="1dp"/>
<solid android:color="#FFCE00" />
</shape>
</clip>
</item>
</layer-list>
好了,前期的准备工作做完了,我们可以进入Activity里面看具体实现PDFReaderActivity.java:
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.format.Formatter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.view.View;
import android.widget.TextView;
import com.yanzhenjie.nohttp.Headers;
import com.yanzhenjie.nohttp.NoHttp;
import com.yanzhenjie.nohttp.download.DownloadListener;
import com.yanzhenjie.nohttp.download.DownloadRequest;
import java.io.File;
import es.voghdev.pdfviewpager.library.adapter.PDFPagerAdapter;
/**
* @Description: PDF阅读,可加载assets/SD卡/URL(在线预览);
* 通过URL地址下载PDF文档到本地,然后再浏览;
* @Author: zzj
* @Date: 2018/9/6 17:10
* @Version: 1.0.0
*/
public class PDFReaderActivity extends Activity {
RelativeLayout rlRoot;
private PDFViewPager remotePDFViewPager;
private PDFPagerAdapter pdfPagerAdapter;
private static final String ROOT_PATH = "/mnt/sdcard/pdf";
private String url = "";//"http://cdn.mozilla.net/pdfjs/tracemonkey.pdf" 测试URL
private String fileSize;
private DownloadRequest downloadRequest;
private RelativeLayout rlDownloadContainer;
private TextView tvDlFileName;
private TextView tvDlProgress;
private ProgressBar pbDownload;
private Button btnDownload;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_reader_pdf);
url = "http://cdn.mozilla.net/pdfjs/tracemonkey.pdf";
//文件大小,这里如果自己知道文档的大小,可以传入
fileSize = Formatter.formatFileSize(this, getIntent().getIntExtra("fileSize", 0));
init();
downloadPdf(url, title);
}
private void init() {
rlRoot = findViewById(R.id.rl_pdf_view);
remotePDFViewPager = findViewById(R.id.pdf_view);
}
/**
* @param path 文件本地路径
* 该PDF库只支持5.0及以上的手机,5.0以下的需要第三方软件打开
*/
private void setData(String path) {
//该PDF库只支持api大于等于21,否则用本地第三方软件打开
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (pdfPagerAdapter == null) {
pdfPagerAdapter = new PDFPagerAdapter(this, path);
remotePDFViewPager.setAdapter(pdfPagerAdapter);
} else {
pdfPagerAdapter.notifyDataSetChanged();
}
remotePDFViewPager.addOnPageChangeListener(onPageChangeListener);
setPageNum(remotePDFViewPager.getCurrentItem());
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(path);
intent.setDataAndType(uri, "application/pdf");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
}
}
}
private void setPageNum(int position) {
//设置页面的指示,当前页为position + 1和总页数pdfPagerAdapter.getCount()
}
private void downloadPdf(String url, String fileName) {
File file = new File(ROOT_PATH + "/" + fileName);
if (file.exists()) {
setData(file.getPath());
return;
}
showDownloadView(fileName);
}
private void showDownloadView(String fileName) {
rlDownloadContainer = findViewById(R.id.rl_download_container);
tvDlFileName = findViewById(R.id.tv_file_name);
tvDlProgress = findViewById(R.id.tv_download_progress);
btnDownload = findViewById(R.id.btn_download);
pbDownload = findViewById(R.id.pb_download);
tvDlFileName.setText(fileName);
rlDownloadContainer.setVisibility(View.VISIBLE);
//download String为字符“下载”,自行定义
btnDownload.setText(String.format("%s(%s)", getString(R.string.download), fileSize));
btnDownload.setOnClickListener(v -> {
if (downloadRequest == null) {
downloadRequest = NoHttp.createDownloadRequest(url, ROOT_PATH, fileName, true, false);
CallServer.getInstance().download(0, downloadRequest, downloadListener);
}
});
}
/**
* 页面变化的监听器
*/
private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
setPageNum(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
};
/**
* 下载进度监听
*/
private final DownloadListener downloadListener = new DownloadListener() {
@Override
public void onDownloadError(int what, Exception exception) {
//下载错误
}
@Override
public void onStart(int what, boolean isResume, long rangeSize, Headers responseHeaders, long allCount) {
if (btnDownload != null) {
btnDownload.setVisibility(View.GONE);
tvDlProgress.setVisibility(View.VISIBLE);
pbDownload.setVisibility(View.VISIBLE);
}
}
@Override
public void onProgress(int what, int progress, long fileCount, long speed) {
pbDownload.setProgress(progress);
tvDlProgress.setText(String.format("%s(%s/%s)", getResources().getString(R.string.downloading), Formatter.formatFileSize(PDFReaderActivity.this, fileCount), fileSize));
}
@Override
public void onFinish(int what, String filePath) {
setData(filePath);
if (remotePDFViewPager.getVisibility() != View.VISIBLE) {
remotePDFViewPager.setVisibility(View.VISIBLE);
}
rlDownloadContainer.setVisibility(View.GONE);
}
@Override
public void onCancel(int what) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
//需要手动关闭资源,防止内存泄漏
if (pdfPagerAdapter != null) {
pdfPagerAdapter.close();
}
if (downloadRequest != null) {
downloadRequest.cancel();
}
}
}
该PDF库有一点比较特殊,就是只支持API 21及以上的手机,所以在低于这个版本的时候,我们还是需要用到第三方阅读器进行打开。我们还是画一个整体流程图吧:
结语
PDF的Android阅读实现方式还是很多的,比如使用Adobe官方的SDK,但国内貌似比较少这样的文章,所以需要自己去挑战一下,还有一些就是需要添加一大堆依赖和库等等,会使整体APK变大,至少10M以上增加体积,但本地所采取的方式很好的解决了这一个问题,APK体积增加不大,在能接受的范围内。欢迎拍砖指出错误,同时也欢迎大家点赞关注分享~