Android文档阅读之PDF文档阅读的方案实现

Android文档阅读之PDF文档阅读的方案实现

上一节说到了Office文档的阅读实现方案,通过NoHTTP将文档下载到本地,然后策略阅读,如果还没阅读到上一篇的小伙伴们,可以先移步到上一节,因为这篇包括后面所写的都会用到这个NoHttp。

Android文档阅读之Office文档阅读的方案实现

打开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体积增加不大,在能接受的范围内。欢迎拍砖指出错误,同时也欢迎大家点赞关注分享~

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值