Android开发丶基于mupdf在Android设备上横竖屏查阅pdf文件

有个需要下载pdf文件后查看的功能,而且要完成首尾页跳转,横竖屏查看,恢复屏幕缩放比例的功能,查阅了相关的资料,发现相对比较缺失,大概是市面上相关需求不多,不过既然我们接到了这个需求,就要把它实现。

主流有以下几种框架

1.JoanZapata/android-pdfview

2.barteksc/AndroidPdfViewer

3.MuPDF

本人将以上多种框架都进行整合测试,发现了这样或那样的问题

第一种:

       优点:可以完成横竖屏查阅、首尾页跳转、恢复屏幕缩放比例等基本所有的需求。

       缺点: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <RelativeLayout
        android:id="@+id/rl_back"
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:background="?attr/colorPrimary">
        <LinearLayout
            android:id="@+id/pdfread_orientation"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:paddingRight="10dp">
            <TextView
                android:id="@+id/pdfread_orientation_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:paddingBottom="3dp"
                android:paddingLeft="8dp"
                android:paddingRight="8dp"
                android:paddingTop="3dp"
                android:text="横屏"
                android:textColor="#fff"
                android:textSize="14sp" />
        </LinearLayout>
    </RelativeLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <RelativeLayout
            android:id="@+id/content_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <LinearLayout
            android:id="@+id/pdfread_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_marginBottom="30dp"
            android:layout_marginRight="30dp"
            android:gravity="center_vertical"
            android:orientation="horizontal">
            <Button
                android:id="@+id/pdfread_firstpage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="首页" />
            <TextView
                android:id="@+id/pdfread_currentpage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="1"
                android:textSize="16sp" />
            <Button
                android:id="@+id/pdfread_finalpage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="尾页" />
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

二、打开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()方法,那我们就可以在这里面做点文章了。

打开清单文件

<activity android:name=".PDFReadActivity"
    android:configChanges="orientation|keyboardHidden|screenSize"/>

在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附上!

 资源下载

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值