有个需要下载pdf文件后查看的功能,而且要完成首尾页跳转,横竖屏查看,恢复屏幕缩放比例的功能,查阅了相关的资料,发现相对比较缺失,大概是市面上相关需求不多,不过既然我们接到了这个需求,就要把它实现。
主流有以下几种框架
本人将以上多种框架都进行整合测试,发现了这样或那样的问题
第一种:
优点:可以完成横竖屏查阅、首尾页跳转、恢复屏幕缩放比例等基本所有的需求。
缺点:github作者已停止维护,会出现某些高版本pdf文件无法打开的问题,出现Error Loading Page的错误,此为框架bug,无法修复,遂搁置。
第二种:
优点:可以完成除了横屏显示外的所有功能需求。
缺点:横竖屏显示不能滑到最边的一页,切换横竖屏后会自动换页。
第三种:
优点:可以完成横竖屏查阅、首尾页跳转、恢复屏幕缩放比例等基本所有的需求。
缺点:暂时没有发现,很完美。
经过比对,我们决定采用第三种MuPDF框架。
新建一个project,import module “mupdf”,该module我是在github找到的,之后的上传的demo会提供。
打开settings.gradle,添加‘mupdf’
打开app的build.gradle,添加mupdf的依赖
implementation project(':mupdf')
点击sync,静候几分钟即可完成环境的搭建。
接下来我们描述实现流程
一、新建一个PDFReadActivity,在xml文件中放置一个空布局用于放置pdfview,一个包含两个用于跳转的按钮、一个显示当前页数和总页数的文本,一个用于切换横竖屏的顶部按钮。
activity_pdfread.xml
二、打开PDFReadActivity
1.初始化控件initView()
/**
* 初始化控件
*/
private void initView() {
//显示当前页数文本
currentPage= findViewById(R.id.pdfread_currentpage);
//跳转首页按钮
firstPage= findViewById(R.id.pdfread_firstpage);
//跳转尾页按钮
finalPage= findViewById(R.id.pdfread_finalpage);
//切换横竖屏文字的包裹布局
orientation= findViewById(R.id.pdfread_orientation);
//切换横竖屏文字
orientationTv= findViewById(R.id.pdfread_orientation_tv);
//包裹pdfview的布局
contentView= findViewById(R.id.pdfread_contentView);
firstPage.setOnClickListener(this);
finalPage.setOnClickListener(this);
orientation.setOnClickListener(this);
}
2.初始化pdf文件initFile()。
/**
* 初始化pdf文件
*/
private void initFile() {
try {
InputStream ins = getResources().getAssets().open("chong.pdf");
FileOutputStream fos = new FileOutputStream(new File(getExternalCacheDir() + "/chong.pdf"));
byte[] b = new byte[1024];
while (ins.read(b) != -1) {
fos.write(b);
}
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}
//通过uri获取我们保存的文件
Uri uri = Uri.parse(getExternalCacheDir() + "/chong.pdf");
//将路径格式化,避免非法字符
try {
path= URLDecoder.decode(uri.getPath(),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
该方法中,我们将assets文件夹下的pdf文件通过IO流的方式保存到手机内存指定路径下。之后通过Uri获取该文件即可,同时将该文件路径格式化UTF-8,避免个别异常字符的出现(比如空格字符 “%20”)。
在实际开发过程中,我们会将文件下载到手机内存指定的路径下,同理我们直接通过uri获取,跳过之前的IO流读写环节。
3.完成了文件的路径配置,我们来配置pdfview。
/**
* 初始化pdfview
*/
private void initPDFView() {
core = openFile(path);
if (core != null && core.countPages() == 0) {
core = null;
}
if (core == null || core.countPages() == 0 || core.countPages() == -1) {
Log.e(TAG, "Document Not Opening");
}
if (core != null) {
mDocView = new MuPDFReaderView(this) {
@Override
protected void onMoveToChild(int i) {
if (core == null)
return;
currentpageNum = i;
currentPage.setText(String.format("%d / %d", i + 1,
core.countPages()));
super.onMoveToChild(i);
}
};
mDocView.setAdapter(new MuPDFPageAdapter(this, new FilePicker.FilePickerSupport() {
@Override
public void performPickFor(FilePicker picker) {
}
}, core));
contentView.addView(mDocView);
}
}
private MuPDFCore openFile(String path) {
int lastSlashPos = path.lastIndexOf('/');
mFilePath = new String(lastSlashPos == -1
? path
: path.substring(lastSlashPos + 1));
try {
core = new MuPDFCore(this, path);
// New file: drop the old outline data
} catch (Exception e) {
Log.e(TAG, e.getMessage());
return null;
}
allPageNum = core.countPages();
currentPage.setText(String.format("%d / %d", 1,
core.countPages()));
return core;
}
分析下该方法
mDocView = new MuPDFReaderView(this) {
@Override
protected void onMoveToChild(int i) {
if (core == null)
return;
currentpageNum = i;
currentPage.setText(String.format("%d / %d", i + 1,
core.countPages()));
super.onMoveToChild(i);
}
};
当滑动或者切换页面的时候,我们把 i 值即当前页数值赋给currentPage,通过core.countPages()获取该文件的总页数。赋值后就是这样的
"1/100"
这样就会造成一个现象,当我们打开页面时,因为还没有滑动或者切换页面,所以currentPage并没有赋值。这时候我们打开openfile()方法。在加载成功后就直接通过core.countPages()方法获取总页数,同时当前页数设置为1,这样就解决了该问题。
private MuPDFCore openFile(String path) {
。。。。。。。。。
。。。。。。。。。
allPageNum = core.countPages();
currentPage.setText(String.format("%d / %d", 1,
core.countPages()));
return core;
}
4.这时候我们把程序跑起来,点击首页的按钮进入pdf页面
5.这样pdf文件可以正常打开了,也可以正常滑动,手指缩放比例。接下来我们完成一系列的拓展工作。
6.首先实现跳转到首页跟尾页。
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.pdfread_firstpage:
handler.sendEmptyMessage(0);
break;
case R.id.pdfread_finalpage:
handler.sendEmptyMessage(allPageNum- 1);
break;
}
}
private Handler handler= new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mDocView.setDisplayedViewIndex(msg.what);
}
};
(选做部分)如果跳转前我们已经对该页面进行了缩放还未恢复初始比例的操作,我们在跳转时要将它恢复初始比例
private Handler handler= new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mDocView.setDisplayedViewIndex(msg.what);
mDocView.refresh(false);
}
};
图示如下:
跳转前已经扩大了屏幕比例,点击尾页,成功恢复初始比例。
在实际调试过程中,如果频繁切换首尾页(当然一般人不会做这种操作),会出现render failed、render aborted的错误,会直接crash。原因定为因为频繁切换首尾页而导致pdfview没来得及渲染从而崩溃,这里我们可以提供一种方式。
当切换页面、渲染成功后,我们才把首页和尾页两个按钮设为可点击状态,当前页面没渲染成功时,按钮设为不可点击。同时点击按钮时,给监听设置个100毫秒的延迟,这样就解决了问题。
case R.id.pdfread_firstpage:
if (isFlag){
isFlag= false;
firstPage.setEnabled(false);
finalPage.setEnabled(false);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
handler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
break;
case R.id.pdfread_finalpage:
if (isFlag){
isFlag= false;
firstPage.setEnabled(false);
finalPage.setEnabled(false);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
handler.sendEmptyMessage(allPageNum- 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
break;
private Handler handler= new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mDocView.setDisplayedViewIndex(msg.what);
mDocView.refresh(false);
isFlag= true;
finalPage.setEnabled(true);
firstPage.setEnabled(true);
}
};
7.(选做)首尾页跳转已经完成了,坑也踩完了,我们接下来做横竖屏切换
一般情况下,我们都会在平板上横屏查阅pdf文件,以获得更大的感观,所以横屏切换就显得尤为重要了。
话不多少,横屏切起来~
额。。。为啥不能完整显示在屏幕上呢,这时我们想起了那个refresh方法,或许能恢复比例吧
那么问题来了,哪里调用这个方法呢?
这时我们想起了,在切换横竖屏时,如果这清单文件中设置了android:configChanges为orientation|keyboardHidden|screensize
就不会重新调用各生命周期,而是调用onConfigurationChanged()方法,那我们就可以在这里面做点文章了。
打开清单文件
在PDFReadActivity中重写onConfigurationChanged()方法。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
handler.sendEmptyMessage(-1);
}
private Handler handler= new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what== -1){
mDocView.refresh(false);
}
这时我们再把程序跑起来~
完美啊,简直太棒了!!!
8、这时我们再来做右上角的手动点击切换横竖屏的功能,有了前面的手动转屏切换,这里我们处理就更得心应手了。
case R.id.pdfread_orientation:
if (getResources().getConfiguration().orientation== Configuration.ORIENTATION_PORTRAIT){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
break;
同时,我们在切换成功后,需要把右上角的文字改为切换后相反方向的文字,比如当前为竖屏,就为“横屏”,表示点击后就切换到横屏,当前为横屏,就为“竖屏”, 表示点击后就切换到竖屏。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
handler.sendEmptyMessage(-1);
if (getResources().getConfiguration().orientation== Configuration.ORIENTATION_PORTRAIT){
orientationTv.setText("横屏");
}else {
orientationTv.setText("竖屏");
}
}
依旧跑起来~~~
当前为竖屏状态,文字为“横屏”
切换横屏,文字显示"竖屏"
完美啊!
9.接下来做点收尾工作,当我们要退出该界面时,如果当前是竖屏则直接退出,如果是横屏,就先切换成横屏,再点击后退键退出界面。
@Override
public void onBackPressed() {
super.onBackPressed();
if (getResources().getConfiguration().orientation== Configuration.ORIENTATION_LANDSCAPE){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}else {
finish();
}
}
至此全部完成,这就是我跟各类pdf框架大战七个昼夜的成果,特此总结发出来,希望能帮助到大家。
不发demo的博文都不是好博文,相信有很多小伙伴直接划到底部看有木有源码的!Demo附上!
资源下载