可以看PDF资源的Activity

前言:最近项目中有个需求是要求打开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);
            }
        });
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,Activity是Android系统中的四大组件之一,可以用于显示View。Activity是一个与用记交互的系统模块,几乎所有的Activity都是和用户进行交互的,但是如果这样就能说Activity主要是用来显示View就不太正确了。 在深入了解Activity之前,我们先要知道一下MVC设计模式,在JAVAEE 中MVC设计模式已经很经典了,而且分的也比较清晰了,但是在Android中,好多人对MVC在Android开发中的应用不是很清楚,下面我就先来介绍一下MVC在Android开发中的应用: M(Model 模型):Model是应用程序的主体部分,所有的业务逻辑都应该写在这里,在Android中Model层与JavaEE中的变化不大,如:对数据库的操作,对网络等的操作都放在该层(但不是说它们都放在同一个包中,可以分开放,但它们统称为Model层)。 V(View 视图):是应用程序中负责生成用户界面的部分,也是在整个MVC架构中用户唯一可以看到的一层,接收用户输入,显示处理结果;在Android应用中一般采用XML文件里德界面的描述,使用的时候可以非常方便的引入,当然也可以使用JavaScript+Html等方式作为View。 C(Controller控制层)android的控制层的重任就要落在众多的activity的肩上了,所以在这里就要建议大家不要在activity中写太多的代码,尽量能过activity交割Model业务逻辑层处理。 好了,在介绍过Android应用开发中的MVC架构后,我们就可以很明确的知道,在Android中Activity主要是用来做控制的,它可以选择要显示的View,也可以从View中获取数据然后把数据传给Model层进行处理,最后再来显示出处理结果。 介绍过Activity的主要作用后,那么我们就要详细说一下Activity了。 Activity生命周期图 Activity 的生命周期是被以下的函数控制的。 public class Activity extends ApplicationContext { protected void onCreate(Bundle icicle); protected void onStart(); protected void onRestart(); protected void onResume(); protected void onFreeze(Bundle outIcicle); protected void onPause(); protected void onStop(); protected void onDestroy(); } onCreate(Bundle) 函数是你进行初始化的地方,这个也是执行 setContentView(View)函数的地方,setContentView(View)函数可以传入一个由XML 编制的UI界面,可以使UI和具体实现完全分离。 onPause()函数是处理用户离开当前 Activity 的地方。更重要的是,任何在当前 Activity中的任何改变都要在这个函数中提交。 Activity有四种状态: 活动状态,当Activity处于Stack(栈)顶时,就是手机当前的现实屏幕,这是 Activity就 处于activity或者运行状态。 运行但是失去焦点,当Activity还处于运行状态时,但是屏幕是有另外一个Activity 处于文档处于焦点状态,当前的Activity就处于pause。 停止,当Activity被另一个Activity完全覆盖的时候,就被停止了,其实就是虽然在 运行,但是用户却看不见。 结束,当Activity处于pause或者stop时,系统可以结束 Activity,回收资源,这 是Activity就是处于结束状态了。 处于结束状态的是Activity,如果要使用户可见,只要重启才可以。 Activity的响应时间 当前Activity所在的线程为主线程,它的响应时间为5秒,如果在当前运行的Activity中进行耗时的操作且响应时间起过5秒,那么程序就会报ANR错误。所以,这也是不建议在Activity中写太多复杂代码的原因之一。 当然,有些代码只能写在Activity中,不然就运行不了(它们不是生命周期方法),比如你想要获得android系统或者硬件一的些信息,就必须在Activity中写出来,如果单独写一个工具类获得不了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值