前言:最近项目中有个需求是要求打开PDF资源图书,因此记录下来
一、选用框架
这里提供3中可选方案
1.使用外部PDF软件打开,直接Intent到该软件就行。
2.使用第三方框架 AndroidPdfViewer
地址:GitHub - barteksc/AndroidPdfViewer: Android view for displaying PDFs rendered with PdfiumAndroid
该第三方库 会使Apk 体积增大15M 左右。
3.使用PDF.js
下载地址:Getting Started
使用Stable稳定版本。下载后把解压文件放入assets目录下
这里直接拖拽文件夹进入Studio可能会提示Error,我是在本地目录拖进去,再用Studio打开
该库 会使Apk 体积增大5M左右。
可以把PDF.js 放大服务器上,完后用cdn的方式打开
参考链接:https://www.jb51.net/article/136364.htm
综合考虑 项目中我使用的是PDF.js 打开资源。该库有丰富的功能,可以支持搜索,页码跳转,横、竖浏览等等符合我的项目需求
但是,我的三星S6(美版)使用的时候在滑动的时候有点卡,使用HUAWEI平板滑动流畅许多(也不满意)。我观察它在每次浏览PDF新页码时会有大量JS操作,也可能和我的测试PDF资源有关(230多页),这里有时间再去优化了。如果有好招留言给我。
二、目录结构
三、修改viewer.js
因为项目中,有的PDF资源没有带目录(如果保证都有目录,则可以跳过该步)
所以,我要修改js,增加点击目录时可以跳转相应位置
在viewer.js中搜索setInitialView方法
setInitialView: function setInitialView(storedHash) {
var _this6 = this;
var _ref7 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
rotation = _ref7.rotation,
sidebarView = _ref7.sidebarView,
scrollMode = _ref7.scrollMode,
spreadMode = _ref7.spreadMode;
var setRotation = function setRotation(angle) {
if ((0, _ui_utils.isValidRotation)(angle)) {
_this6.pdfViewer.pagesRotation = angle;
}
};
var setViewerModes = function setViewerModes(scroll, spread) {
if ((0, _ui_utils.isValidScrollMode)(scroll)) {
_this6.pdfViewer.scrollMode = scroll;
}
if ((0, _ui_utils.isValidSpreadMode)(spread)) {
_this6.pdfViewer.spreadMode = spread;
}
};
this.isInitialViewSet = true;
this.pdfSidebar.setInitialView(sidebarView);
setViewerModes(scrollMode, spreadMode);
if (this.initialBookmark) {
setRotation(this.initialRotation);
delete this.initialRotation;
this.pdfLinkService.setHash(this.initialBookmark);
this.initialBookmark = null;
} else if (storedHash) {
setRotation(rotation);
this.pdfLinkService.setHash(storedHash);
}
this.toolbar.setPageNumber(this.pdfViewer.currentPageNumber, this.pdfViewer.currentPageLabel);
this.secondaryToolbar.setPageNumber(this.pdfViewer.currentPageNumber);
if (!this.pdfViewer.currentScaleValue) {
this.pdfViewer.currentScaleValue = _ui_utils.DEFAULT_SCALE_VALUE;
}
//以下代码为了跳转固定页面 1521行-1540行
var c_url=window.location.href;
if(c_url.indexOf("&")&&c_url.indexOf("=")){
var c_urlArray={}
var c_val=c_url.split('?')[1];
var c_valArray=c_val.split('&');
for(let i=0;i<c_valArray.length;i++){
let c_key=c_valArray[i].split('=')[0];
let c_value=c_valArray[i].split('=')[1];
c_urlArray[c_key]=c_value;
}
if(c_urlArray['zoom']){
this.pdfViewer.currentScale=c_urlArray['zoom'];
}
if(c_urlArray['page']){
document.getElementById('pageNumber').value = this.pdfViewer.currentPageNumber = c_urlArray['page']*1;
}
if(c_urlArray['top']){
document.getElementById('viewerContainer').scrollTop=document.getElementById('viewerContainer').scrollTop+c_urlArray['top']*1;
}
}
},
至此、准备工作已经做完了!下面是个Activity作为我的PDF阅读器浏览页面
四、Activity
该布局中就提供个WebView就行
public class ReaderPdfViewActivity extends BaseActivity {
private WebView mWebView;
private TextView mMuLuTv;
private RelativeLayout mCatalogLayout;
private RecyclerView mRecycler;
private String pdfXMLURL;
private String pdfFilePath;
private List<CatalogBean> catalogList;
private Handler mHandler = new Handler();
@Override
protected int setLayoutResource() {
return R.layout.retech_ac_pdfview;
}
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void initView() {
mWebView = findViewById(R.id.webView);
mMuLuTv = findViewById(R.id.tv_mulu);
mCatalogLayout = findViewById(R.id.rl_catalog);
mRecycler = findViewById(R.id.recyclerView);
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
@Override
protected void initListener() {
mMuLuTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mCatalogLayout.getVisibility() == View.VISIBLE) {
mCatalogLayout.setVisibility(View.GONE);
return;
}
if (catalogList == null) {
new Thread(downable).start();
} else {
openCatalog(catalogList);
}
}
});
}
@Override
protected void initData() {
pdfFilePath = getIntent().getStringExtra(IntentConstant.Intent_PDF_PATH);
pdfXMLURL = getIntent().getStringExtra(IntentConstant.Intent_PDF_XML);
if (TextUtils.isEmpty(pdfXMLURL)) {
//没有目录资源
mMuLuTv.setVisibility(View.GONE);
} else {
mMuLuTv.setVisibility(View.VISIBLE);
}
File file = new File(pdfFilePath);
if (!file.exists()) {
ToastUtils.show("无效的 pdf 文件");
finish();
} else {
mWebView.loadUrl("file:///android_asset/pdfjs/web/viewer.html?file=" + "file:///" + pdfFilePath);
}
}
@Override
protected void onDestroy() {
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView = null;
super.onDestroy();
}
//下载事件 在线程中
private Runnable downable = new Runnable() {
@Override
public void run() {
try {
URL url = new URL(pdfXMLURL);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//发送http GET请求,获取相应码
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
//使用pull解析器,开始解析这个流
parseNewsXml(is);
}
} catch (Exception e) {
showToast("目录下载失败");
e.printStackTrace();
}
}
};
//解析XML 在线程中
private void parseNewsXml(InputStream is) {
XmlPullParser xpp = Xml.newPullParser();
try {
xpp.setInput(is, "utf-8");
//对节点事件类型进行判断
int type = xpp.getEventType();
CatalogBean catalogBean = null;
List<CatalogBean> newList = new ArrayList<>();
while (type != XmlPullParser.END_DOCUMENT) {//判断节点是否到最后
switch (type) {
case XmlPullParser.START_TAG://当前节点开始
if ("title".equals(xpp.getName())) {//title标签开始
catalogBean = new CatalogBean();
catalogBean.setId(Integer.parseInt(xpp.getAttributeValue(0)));
catalogBean.setName(xpp.nextText());
newList.add(catalogBean);
}
break;
case XmlPullParser.END_TAG://当前节点结束
break;
}
//解析完当前节点后,把指针移动至下一个节点,直至节点完毕,并返回它的事件类型
type = xpp.next();
}
if (newList.size() > 0) {
catalogList = newList;
}
//到主线程中进行操作
mHandler.post(new Runnable() {
@Override
public void run() {
openCatalog(catalogList);
}
});
if (is != null) {
is.close();
}
} catch (Exception e) {
showToast("目录解析失败");
e.printStackTrace();
}
}
//打开目录 在主线程中
private void openCatalog(List<CatalogBean> catalogList) {
if (catalogList == null) {
ToastUtils.show("无法获取目录");
} else if (catalogList.size() == 0) {
ToastUtils.show("暂无目录");
} else {
CatalogAdapter adapter = new CatalogAdapter(this);
adapter.addList(catalogList);
adapter.setOnItemListener(new CatalogAdapter.OnItemListener() {
@Override
public void onClick(View v, int position, int id) {
//参考:https://www.jianshu.com/p/8e7f4c68d947
//修改viewer.js 的1521行-1540行源码
mWebView.loadUrl("file:///android_asset/pdfjs/web/viewer.html?file=" + "file:///" + pdfFilePath + "&page=" + id + "&top=150");
mCatalogLayout.setVisibility(View.GONE);
}
});
mRecycler.setAdapter(adapter);
mCatalogLayout.setVisibility(View.VISIBLE);
}
}
//在主线程中吐司
private void showToast(final String text) {
mHandler.post(new Runnable() {
@Override
public void run() {
ToastUtils.show(text);
}
});
}
}