一款集成簡(jiǎn)單翅萤、擴(kuò)展方便的閱讀器框架 <一>

*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

先看擁有部分功能的效果圖套么。

20181123_204344.gif

最近小說(shuō)很火呀碳蛋!刷抖音刷出好多小說(shuō)的廣告!

在小說(shuō)軟件中玷室,閱讀器當(dāng)屬最復(fù)雜但也是最重要跟用戶交互最多的模塊了笤受。

看了各大廠商的閱讀器,如掌閱绅项、追書(shū)神器比肄、愛(ài)奇藝閱讀、百度閱讀掀亥、網(wǎng)易云閱讀、鯨魚(yú)閱讀等等妥色,其中掌閱的體驗(yàn)最好搪花。而百度閱讀、網(wǎng)易云閱讀等等的閱讀器做的還時(shí)比較糙的嘹害,有明顯的翻頁(yè)丟幀撮竿,因?yàn)檫@塊邏輯實(shí)在是復(fù)雜。

網(wǎng)上也有一些教程笔呀,基本上就是講解翻頁(yè)效果沒(méi)有真正的一個(gè)跟業(yè)務(wù)直接對(duì)接的框架幢踏,所以我決定寫(xiě)一個(gè)Reader框架(就叫Reader吧),它必須具備這三個(gè)特點(diǎn):1.業(yè)務(wù)解耦许师,跟業(yè)務(wù)邏輯完全解耦房蝉;2.集成簡(jiǎn)單僚匆;3.易于擴(kuò)展,進(jìn)而能夠應(yīng)付各種需求搭幻。(當(dāng)然目前還有很多不完善的地方...還有一些功能慢慢加)

花了十幾天的時(shí)間咧擂,終于完工了。

一款小說(shuō)類(lèi)APP閱讀器的業(yè)務(wù)邏輯

首先我們需要知道一款小說(shuō)類(lèi)app加載閱讀器的業(yè)務(wù)流程檀蹋。大致的流程是:

1.先獲取章節(jié)列表(目錄)松申,數(shù)據(jù)大約長(zhǎng)這個(gè)樣子

[
    {
        "chapterId":"00001",
        "chapterName":"第一章 郭芙蓉"
    },
    {
        "chapterId":"00002",
        "chapterName":"第二章 呂秀才"
    },
    {
        "chapterId":"00003",
        "chapterName":"第三章 白大俠"
    },
    ...等等
] 

2.然后通過(guò)章節(jié)列表中的一個(gè)Item獲取章節(jié)內(nèi)容,數(shù)據(jù)大約長(zhǎng)這個(gè)樣子

{
      "chapterId":"00001",
      "cahpterName":"郭芙蓉",
      "content":"年紀(jì)輕輕為追求自由尋求真正的江湖道義就敢于離開(kāi)父母的庇護(hù)背井離鄉(xiāng)一個(gè)人跑出來(lái)闖蕩续扔,按照及性格和父親身份分析气破,郭芙蓉是千金小姐埠偿,不管發(fā)生什么都能一臉不在乎相信一切都會(huì)變好辆它,因?yàn)楦赣H是大俠中的大俠缀去,八個(gè)師兄又從小和父親習(xí)武成為六扇門(mén)神捕,她也很向往這種生活识脆,也想成為父親一樣大俠中的大俠,闖蕩江湖來(lái)到同咐肜客棧找到了所向往的生活后安定下來(lái),和秀才是一對(duì)歡喜冤家耀盗,幾十年后有兩個(gè)女兒叛拷,一個(gè)是小女兒龍門(mén)鏢局的鏢師呂青橙...."
}

3.最后閱讀器將章節(jié)內(nèi)容分成N頁(yè)忿薇,并分頁(yè)顯示署浩。

Reader就是基于這種業(yè)務(wù)邏輯提供了如下功能:

  • 分頁(yè)筋栋、翻頁(yè)功能

  • 翻頁(yè)動(dòng)效(目前有仿真(單項(xiàng))、仿真(雙向)肴颊、覆蓋婿着、滑動(dòng)(左右)竟宋、無(wú)效果)

  • 緩存系列功能(增丘侠、刪、改脂新、查)

  • 跳轉(zhuǎn)指定章節(jié)级零、指定文字位置

  • 自定義分段符

  • 背景、文字顏色酷宵、大小炕置、行間距等設(shè)置

  • 正文位置設(shè)置

總而言之朴摊,只需要指定業(yè)務(wù)邏輯中,章節(jié)列表Item和章節(jié)內(nèi)容的數(shù)據(jù)格式介杆,便可以工作了春哨。集成和用法都比較簡(jiǎn)單椰拒。

集成Reader

發(fā)布到j(luò)center時(shí)遇到個(gè)問(wèn)題燃观,各種搜索還是沒(méi)搞定,最近又裸辭了积锅,還得抽時(shí)間準(zhǔn)備找工作,所以上傳jcenter后續(xù)再搞往核。

源碼地址

下載源碼后將Reader文件放在項(xiàng)目得根目錄虎锚,作為Model即可窜护。

Reader的簡(jiǎn)單使用

  • 首先,在XML中配置View奇昙,跟所有View一樣羊初,無(wú)需多說(shuō)
      <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
          ...省略部分無(wú)關(guān)代碼>
      ?
          <com.glong.reader.widget.ReaderView
              android:id="@+id/simple_reader_view"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
      ?
      </android.support.constraint.ConstraintLayout>

在Activity的onCreate()方法中通過(guò)findViewById或注解方式獲取ReaderView實(shí)例

  • 給ReaderView設(shè)置ReaderManager
     private void initReader() {
              mReaderView = findViewById(R.id.simple_reader_view);
      ?
              mReaderManager = new ReaderView.ReaderManager();
              mReaderView.setReaderManager(mReaderManager);
          }
  • 設(shè)置Adapter<K,T>(敲黑板晦攒,劃重點(diǎn),前年20分大題柳恐,去年沒(méi)考,今年必考...)

    其中泛型K代表章節(jié)列表Item的數(shù)據(jù)類(lèi)型近尚,T代表章節(jié)內(nèi)容的數(shù)據(jù)類(lèi)型。假如我們的業(yè)務(wù)邏輯章節(jié)列表Item數(shù)據(jù)類(lèi)型是ChapterItemBean格遭,章節(jié)內(nèi)容數(shù)據(jù)類(lèi)型是BookContentBean,可按如下方式設(shè)置Adapter

mReaderView.setAdapter(new ReaderView.Adapter<ChapterItemBean, ChapterContentBean>() {
      ?
              @Override
              public String obtainCacheKey(ChapterItemBean chapterItemBean) {
                  return chapterItemBean.getChapterId() + userId;
              }
      ?
              @Override
              public String obtainChapterName(ChapterItemBean chapterItemBean) {
                  return chapterItemBean.getChapterName();
              }
      ?
              @Override
              public String obtainChapterContent(ChapterContentBean contentBean) {
                  return contentBean.getChapterContent();
              }
      ?
              /**
               * 這個(gè)方法運(yùn)行在子線程中璧微,同步返回章節(jié)內(nèi)容
               */
              @Override
              public ChapterContentBean downLoad(ChapterItemBean chapterItemBean) {
                  return LocalServer.syncDownloadContent(chapterItemBean);
              }
          });

看到Adapter的代碼荧止,有小伙伴可能要感嘆罩息,臥槽葱色,這么復(fù)雜办龄,右上角紅x不謝淋昭。

稍安勿躁,先想想驶悟,為什么要有Adapter?

因?yàn)槲覀兊腞eader是跟業(yè)務(wù)邏輯解耦的笼呆,同時(shí)又是跟業(yè)務(wù)邏輯直接對(duì)接的。Reader不知道業(yè)務(wù)邏輯要用什么key緩存章節(jié)內(nèi)容秸弛;雖然拿到了ChapterItemBean但Reader不知道章節(jié)標(biāo)題是哪個(gè)字段境肾;雖然拿到了BookContentBean但Reader不知道章節(jié)內(nèi)容是哪個(gè)字段。

這就好比生活中的插座和電子設(shè)備的關(guān)系胆屿,插座可以給任何電子設(shè)備供電奥喻,但是插座又不知道你到底插的是電腦還是手機(jī)還是剃須刀,不知道你要多大的電壓非迹,所以就有了變壓器环鲤。往往電子設(shè)備提供變壓器。(Adapter適配器)

同樣的道理憎兽,Adapter將數(shù)據(jù)類(lèi)型JavaBean轉(zhuǎn)換成閱讀器真正需要的字符瞭空。

" soga,那download又是什么鬼?"

前面我們講閱讀器業(yè)務(wù)邏輯的時(shí)候,說(shuō)到,小說(shuō)類(lèi)app是先拿到章節(jié)列表辅柴,然后通過(guò)章節(jié)列表里的一個(gè)Item獲取章節(jié)內(nèi)容,這個(gè)download(ChapterItemBean chapterItemBean)就是通過(guò)章節(jié)列表的一個(gè)Item獲取章節(jié)內(nèi)容的怯疤。

" Reader不會(huì)下載么?"

Reader不知道Url呀妨马,不知道參數(shù)梧喷,不知道HTTP 方式弯囊,不知道body 贝或,不知道Header扇谣。簸淀。歉眷。

這樣啊霍骄,那如果,我說(shuō)如果屈糊,我把下載的url參數(shù)都告訴你呢凶掰?

嗯愤惰,那就可以不用在download(ChapterItemBean chapterItemBean)實(shí)現(xiàn)同步下載了践盼,直接讓他返回null就行了,但需要重寫(xiě)Adapter的requestParams(ChapterItemBean chapterItemBean)方法

          @Override
          public Request requestParams(ChapterItemBean chapterItemBean) {
              return new Request.Builder()
                      .addHeader("token", "userToken")
                      .addUrlParams("bookId", "123")
                      .addUrlParams("bookName", "123")
                      .addBody("chapterId", chapterItemBean.getChapterId())
                      .post()
                      .build();
      } 

”這下是不是簡(jiǎn)單了很多谅河?“

這樣簡(jiǎn)單了很多咱旱,但是還是覺(jué)得...

閉嘴确丢,你個(gè)地中海!

...

  • 最后獲取章節(jié)列表吐限,調(diào)用notifyDataSetChanged()即可
          /*
           * 獲取章節(jié)列表
           */
           LocalServer.getChapterList("123", new LocalServer.OnResponseCallback() {
              @Override
              public void onSuccess(List<ChapterItemBean> chapters) {
                  mAdapter.setChapterList(chapters);

                  mAdapter.notifyDataSetChanged();
              }
      ?
              @Override
              public void onError(Exception e) {
      ?
              }
          });

運(yùn)行一下我們的項(xiàng)目鲜侥,我們已經(jīng)迫不及待的想看一下效果了

簡(jiǎn)單的集成

僅僅需要這幾步我們就實(shí)現(xiàn)了一個(gè)具有翻頁(yè)功能的閱讀器,是不是很簡(jiǎn)單诸典?

拿蔥的大嬸說(shuō)話了:“你這簡(jiǎn)單個(gè)毛啊描函,又是ReaderManager又是Adapter的,你就不能直接封裝一個(gè)ReaderView狐粱,我直接對(duì)著ReaderView擼不更簡(jiǎn)單舀寓?

大嬸你是干嘛的?

俺是耕田滴

“鋤頭是鋤頭肌蜻,鏟子是鏟子互墓,為什么不把鏟子跟鋤頭組裝在一塊,然后不管是鋤地還是鏟shi只拿一個(gè)家伙事呢蒋搜?”

假如所有的API接口都封裝在ReaderView中篡撵,在通過(guò)ReaderView的實(shí)例調(diào)用方法時(shí),IDE 的智能提示會(huì)發(fā)現(xiàn)一大堆的方法豆挽,很懵育谬。這也違背了java的單一職責(zé)基本原則,盡管這個(gè)原則爭(zhēng)議較大帮哈,但是此處我相信大家都沒(méi)啥爭(zhēng)議膛檀。

大家常用RecyclerView,可以把ReaderView當(dāng)作RecyclerView娘侍,把ReaderManager當(dāng)作LayoutManager咖刃,把ReaderView#Adapter當(dāng)作RecyclerView#Adapter,是不是簡(jiǎn)單了很多私蕾。事實(shí)上僵缺,他們的用法以及功能劃分也及其相似胡桃。

基本設(shè)置

基本設(shè)置也很簡(jiǎn)單啦踩叭。

"你每次都說(shuō)很簡(jiǎn)單,然而我看你又要吹牛逼了"搬磚那哥們拿起手中的磚說(shuō)道翠胰。

relax容贝!哥們,放下手中的磚之景!聽(tīng)我講斤富。

1.ReaderView#setAdapter(Adapter adapter)

設(shè)置適配器,前面已經(jīng)用過(guò)了锻狗,跟RecyclerView的setAdapter()類(lèi)似满力;

2.ReaderView#setReaderManager(ReaderManager readerManager)

跟RecyclerView的setLayoutManager類(lèi)似焕参,關(guān)于ReaderManager以及它的API后面詳細(xì)說(shuō);

3.ReaderView#setTextSize(int textSize)

設(shè)置閱讀器文字大小

4.View#setBackground(xxxxxx)油额、View#setBackgroundColor(int color)

這是View設(shè)置背景的方法叠纷,通過(guò)原生的設(shè)置View背景的方法設(shè)置Reader的背景,可以是color潦嘶、Bitmap涩嚣、Drawable;

5.ReaderView#setLineSpace(int lineSpace)

設(shè)置閱讀器文字間距掂僵;

6.ReaderView#setBodyTextPadding(int[] padding)

設(shè)置閱讀器正文距離View上下左右邊界的位置的位置航厚,padding是長(zhǎng)度為4的int數(shù)組,元素0~3分別代表left锰蓬、top幔睬、right、bottom四個(gè)值互妓;

7.ReaderView#setBatteryWidthAndHeight(int[] widthAndHeight)

設(shè)置電池的長(zhǎng)寬溪窒,參數(shù)widthAndHeight是長(zhǎng)度為2的int數(shù)組,元素0~1分別代表電池的寬度和高度

能否自定義電池冯勉?可以的澈蚌,后面將擴(kuò)展的時(shí)候詳細(xì)說(shuō);

8.ReaderView#setColorsConfig(ColorsConfig colorsConfig)

設(shè)置界面顏色相關(guān)的對(duì)象灼狰,ColorsConfig是所有顏色的封裝宛瞄,比如電池顏色、文字顏色...

為什么要把顏色封裝交胚,不能像setTextSize()一樣直接放在ReaderView里面份汗?

因?yàn)橐话闱闆r下背景和其他元素的顏色都是對(duì)應(yīng)的,比如當(dāng)設(shè)置了黑色的背景時(shí)蝴簇,這個(gè)時(shí)候文字顏色應(yīng)該設(shè)置為白色杯活,電池顏色也應(yīng)該設(shè)置為白色;

9.ReaderView#setEffect(@NonNull Effect effect)

設(shè)置閱讀器的翻頁(yè)動(dòng)效熬词,目前已有動(dòng)效如下表格

動(dòng)效 描述 父類(lèi)
EffectOfRealOneWay 仿真(單向) Effect
EffectOfRealBothWay 仿真(雙向) Effect
EffectOfCover 覆蓋 Effect
EffectOfSlide 滑動(dòng)(左右滑動(dòng)) Effect
EffectOfNon 無(wú)效果(瞬變) Effect

可不可以自定義翻頁(yè)動(dòng)效旁钧?

of course!只需要繼承自Effect即可互拾;

10.ReaderView#setPageChangedCallback(@NonNull PageChangedCallback pageChangedCallback)

閱讀器翻頁(yè)的回調(diào)歪今;

11.ReaderView#setPageDrawingCallback(@NonNull PageDrawingCallback pageDrawingCallback)

閱讀器需要刷新頁(yè)面時(shí)的回調(diào)(一般用不到)

12.ReaderView#invalidateCurrPage()

刷新當(dāng)前頁(yè);

不推薦使用

13.ReaderView#invalidateNextPage()

刷新下一頁(yè)颜矿;

不推薦使用

14.ReaderView#invalidateBothPage()

刷新當(dāng)前頁(yè)和下一頁(yè)寄猩;

墻裂建議使用該方法

當(dāng)需要主動(dòng)刷新當(dāng)前閱讀器UI時(shí),推薦使用invalidateBothPage()方法骑疆。

我只需要刷新當(dāng)前頁(yè)田篇,不需要刷新下一頁(yè)(下一頁(yè)用戶也看不到疤娣稀),為什么還要兩頁(yè)同時(shí)刷新泊柬?

の舶担,這個(gè)還真不太好解釋,因?yàn)檫@里的上一頁(yè)/下一頁(yè)和我們理解的上一頁(yè)/下一頁(yè)并不太一樣彬呻,就好比我們看到的3D效果其實(shí)也是2D實(shí)現(xiàn)的衣陶,都是騙眼睛的。當(dāng)翻向下一頁(yè)的時(shí)候我們看到的是下一頁(yè)闸氮,當(dāng)翻向上一頁(yè)的時(shí)候我們看到的其實(shí)還是下一頁(yè)剪况。有點(diǎn)繞,這其實(shí)跟軟件的設(shè)計(jì)有關(guān)蒲跨,翻頁(yè)設(shè)計(jì)時(shí)只有當(dāng)前頁(yè)和下一頁(yè)译断,沒(méi)有上一頁(yè)(節(jié)省內(nèi)存),那翻向上一頁(yè)怎么辦或悲?這里的上一頁(yè)也就是下一頁(yè)孙咪。

總之,只有理解原理后才建議調(diào)用invalidateCurrPage()invalidateNextPage()巡语。否則就調(diào)用invalidateBothPage()確保顯示沒(méi)有問(wèn)題翎蹈。

婆婆媽媽說(shuō)了一大堆,總結(jié)就倆字:當(dāng)需要刷新界面時(shí)使用invalidateBothPage()男公。

15.ReaderView#addParagraph(String paragraph)

增加分段符荤堪;

ReaderView默認(rèn)的分段符有<br><br><br>枢赔、</p>澄阳,比如你的業(yè)務(wù)邏輯中是使用sb分段的,那么只需要調(diào)用ReaderView#addParagraph("sb")踏拜,比如你的業(yè)務(wù)邏輯使用</sb>分段的那就傳入</sb>即可碎赢。

16.ReaderView#getBodyTextPaint()

獲取正文畫(huà)筆;

有了畫(huà)筆速梗,可以設(shè)置字體肮塞、文字的各種樣式等等。

比如要設(shè)置字體:

  Paint paint = mReaderView.getBodyTextPaint();
  Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
  paint.setTypeface( font );
  ?
  // 刷新頁(yè)面
  mReaderView.invalidateBothPage();

17.其他ReaderView#getxxx()方法

省略...

OK镀琉,ReaderView的API基本介紹完了峦嗤,根據(jù)這些API我們稍微修改上面已經(jīng)寫(xiě)好的閱讀器蕊唐。效果圖如文章最開(kāi)始展示的效果圖屋摔。

省略xml布局文件中添加Button;

省略findViewById(id)替梨、setOnClickListener(View.OnClickListener listener)操作钓试;

收到點(diǎn)擊事件后設(shè)置相關(guān)的背景和翻頁(yè)動(dòng)效装黑;

      @Override
      public void onClick(View v) {
          switch (v.getId()) {
              case R.id.reader_bg_0:
                  mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_0));
                  break;
              case R.id.reader_bg_1:
                  mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_1));
                  break;
              case R.id.reader_bg_2:
                  mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_2));
                  break;
              case R.id.reader_bg_3:
                  mReaderView.setBackgroundColor(getResources().getColor(R.color.reader_bg_3));
                  break;
              case R.id.effect_real_one_way:
              case R.id.effect_default:
                  mReaderView.setEffect(new EffectOfRealOneWay(this));
                  break;
              case R.id.effect_real_both_way:
                  mReaderView.setEffect(new EffectOfRealBothWay(this));
                  break;
              case R.id.effect_cover:
                  mReaderView.setEffect(new EffectOfCover(this));
                  break;
              case R.id.effect_slide:
                  mReaderView.setEffect(new EffectOfSlide(this));
                  break;
              case R.id.effect_non:
                  mReaderView.setEffect(new EffectOfNon(this));
                  break;
          }
      }

動(dòng)態(tài)設(shè)置文字大小textSize和行間距l(xiāng)ineSpace

          SeekBar textSizeSeek = findViewById(R.id.text_size_seek_bar);
          textSizeSeek.setMax(100);
          textSizeSeek.setProgress(mReaderView.getTextSize());
          textSizeSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
              @Override
              public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                  mReaderView.setTextSize(progress);
              }
        // 省略無(wú)關(guān)代碼
          });
  ?
          SeekBar lineSpaceSeek = findViewById(R.id.line_space_seek_bar);
          lineSpaceSeek.setMax(100);
          lineSpaceSeek.setProgress(mReaderView.getLineSpace());
          lineSpaceSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
              @Override
              public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                  mReaderView.setLineSpace(seekBar.getProgress());
              }
        // 省略無(wú)關(guān)代碼
          });

這里沒(méi)有顏色相關(guān)設(shè)置,其用法也比較簡(jiǎn)單弓熏,就是簡(jiǎn)單的調(diào)用方法恋谭,這里不再贅述。

自定義PageChangedCallback回調(diào)挽鞠。比如疚颊,在翻頁(yè)時(shí),當(dāng)沒(méi)有上一頁(yè)/下一頁(yè)時(shí) 彈出Toast提示

 mReaderView.setPageChangedCallback(new PageChangedCallback() {
              @Override
              public TurnStatus toPrevPage() {
                  TurnStatus turnStatus = readerManager.toPrevPage();
                  if (turnStatus == TurnStatus.NO_PREV_CHAPTER) {
                      Toast.makeText(NormalReaderActivity.this, "沒(méi)有上一頁(yè)啦", Toast.LENGTH_SHORT).show();
                  }
                  return turnStatus;
              }
  ?
              @Override
              public TurnStatus toNextPage() {
                  TurnStatus turnStatus = readerManager.toNextPage();
                  if (turnStatus == TurnStatus.NO_NEXT_CHAPTER) {
                      Toast.makeText(NormalReaderActivity.this, "沒(méi)有下一頁(yè)啦", Toast.LENGTH_SHORT).show();
                  }
                  return turnStatus;
              }
          });

通過(guò)setPageChangedCallback()設(shè)置PageChangedCallback信认,可以監(jiān)聽(tīng)當(dāng)滑向下一頁(yè)/上一頁(yè)時(shí)的狀態(tài)材义。枚舉TurnStatus共有五種狀態(tài);

枚舉值 描述
IDLE 空閑狀態(tài)嫁赏,未翻頁(yè)
LOAD_SUCCESS 加載上一頁(yè)/下一頁(yè)/指定章節(jié) 成功
LOAD_FAILURE 加載失斊涞唷(如下載失敗)
DOWNLOADING 正在下載
NO_NEXT_CHAPTER 沒(méi)有上一章了(在第一章第一頁(yè)時(shí)潦蝇,再往前翻就會(huì)返回這個(gè)狀態(tài))
NO_PREV_CHAPTER 沒(méi)有下一章了(再最后一章最后一頁(yè)時(shí)款熬,再往后翻就會(huì)返回這個(gè)狀態(tài))

當(dāng)然,通過(guò)這個(gè)回調(diào)也可以實(shí)現(xiàn)攘乒,當(dāng)翻向下一頁(yè)時(shí)直接跳到下一章贤牛。當(dāng)翻向上一頁(yè)的時(shí)候直接跳到上一章。等等则酝。需要結(jié)合ReaderView#ReaderManager API實(shí)現(xiàn)盔夜。

ReaderView#ReaderManager 的API

1.TurnStatus toPrevPage() & TurnStatus toNextPage()

這兩個(gè)方法前面已經(jīng)用過(guò)了。意思是將數(shù)據(jù)置為上一頁(yè)/下一頁(yè)(如果當(dāng)前章節(jié)已經(jīng)在第一頁(yè)/最后一頁(yè)時(shí)堤魁,自動(dòng)跳到下一章/上一章)喂链。

比如通過(guò)音量鍵翻頁(yè),重寫(xiě)Activity的onKeyDown和onKeyUp方法

 @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
      if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
          mReaderManager.toNextPage();
          mReaderView.invalidateBothPage();
          return true;// 返回ture防止翻頁(yè)有聲音
      } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
          mReaderManager.toPrevPage();
          mReaderView.invalidateBothPage();
          return true;
      }
      return super.onKeyUp(keyCode, event);
  }
  ?
  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event) {
      switch (keyCode) {
          case KeyEvent.KEYCODE_VOLUME_DOWN:
              return true;//返回turn 不彈出音量控件
          case KeyEvent.KEYCODE_VOLUME_UP:
              return true;
      }
      return super.onKeyDown(keyCode, event);
  }

因?yàn)?code>toPrevPage() /toNextPage()僅僅是將數(shù)據(jù)置為上一頁(yè)/下一頁(yè)妥泉。所以椭微,還需要調(diào)用ReaderView#invalidateBothPage()刷新頁(yè)面。

前面設(shè)置PageChangedCallback時(shí)為什么不調(diào)用ReaderView#invalidateBothPage()呢盲链?

因?yàn)镻ageChangedCallback是ReaderView的回調(diào)蝇率,其內(nèi)部已經(jīng)實(shí)現(xiàn)了刷新邏輯。務(wù)必不要調(diào)用刷新頁(yè)面的任何方法;

2.TurnStatus toPrevChapter() & TurnStatus toNextChapter()

跳轉(zhuǎn)到上一章最后字符(即最后一頁(yè)) & 跳轉(zhuǎn)到下一章第一個(gè)字符(即第一頁(yè))刽沾。如果是手動(dòng)跳轉(zhuǎn)需要刷新頁(yè)面本慕;

3.TurnStatus toPrevChapter(int charIndex) & TurnStatus toNextChapter(int charIndex)

跳轉(zhuǎn)到上一章指定字符 & 跳轉(zhuǎn)到下一章指定字符 , -1代表最后一個(gè)字符侧漓。如果是手動(dòng)跳轉(zhuǎn)需要刷新頁(yè)面锅尘;

4.TurnStatus toSpecifiedChapter(final int chapterIndex, final int charIndex)

跳轉(zhuǎn)到指定章節(jié)的指定字符位置,比如要跳轉(zhuǎn)到第5章的第一頁(yè)toSpecifiedChapter(4,0)布蔗,要跳轉(zhuǎn)到第5章的最后一頁(yè)toSpecifiedChapter(4,-1)藤违。如果是手動(dòng)跳轉(zhuǎn)需要刷新頁(yè)面浪腐;

5.setCache(Cache cache)

設(shè)置緩存。如果沒(méi)有設(shè)置顿乒,ReaderManager會(huì)設(shè)置一個(gè)默認(rèn)的緩存议街;

6.setCustomReaderResolve(ReaderResolve readerResolve)

設(shè)置自定義的ReaderResolve。ReaderResolve處理所有頁(yè)面上計(jì)算璧榄、具體畫(huà)文字特漩、圖標(biāo)等等。

7.setOnReaderWatcherListener(OnReaderWatcherListener onReaderWatcherListener)

關(guān)于OnReaderWatcherListener骨杂,看OnReaderWathcListener的定義拾稳,一目了然

      /**
       * 頁(yè)碼發(fā)生了變化
       *
       * @param pageIndex 第pageIndex頁(yè)(從第0頁(yè)開(kāi)始)
       */
      void onPageChanged(int pageIndex);
  ?
      /**
       * 章節(jié)發(fā)生了變化
       *
       * @param chapterIndex 跳轉(zhuǎn)到了第chapterIndex章
       * @param pageIndex    跳轉(zhuǎn)到了這章的第pageIndex頁(yè)(從第0頁(yè)開(kāi)始)
       */
      void onChapterChanged(int chapterIndex, int pageIndex);
  ?
      /**
       * 開(kāi)始下載當(dāng)前所需章節(jié)時(shí)調(diào)用(方便彈出提示等等)
       * 當(dāng)下載緩存時(shí)不會(huì)回調(diào)
       *
       * @param chapterIndex 章節(jié)索引
       */
      void onChapterDownloadStart(int chapterIndex);
  ?
      /**
       * 當(dāng)前所需章節(jié)下載成功后回調(diào)
       * 僅下載緩存時(shí)不會(huì)回調(diào)
       *
       * @param chapterIndex 章節(jié)索引
       */
      void onChapterDownloadSuccess(int chapterIndex);
  ?
      /**
       * 當(dāng)前所需章節(jié)下載成功后回調(diào)
       * 僅下載緩存時(shí)不會(huì)回調(diào)
       *
       * @param chapterIndex 章節(jié)索引
       */
      void onChapterDownloadError(int chapterIndex);

8.void onAdapterChanged(Adapter oldAdapter, Adapter adapter)

當(dāng)Adapter發(fā)生變化時(shí)回調(diào);

9.startFromCache(String key, int chapterIndex, int charIndex, @NonNull String chapterName)

最開(kāi)始我們說(shuō)了腊脱,小說(shuō)類(lèi)app的業(yè)務(wù)邏輯先獲取章節(jié)列表访得,然后通過(guò)章節(jié)列表的某一項(xiàng)獲取章節(jié)內(nèi)容,最后交給閱讀器顯示陕凹。那么問(wèn)題來(lái)了悍抑,①當(dāng)用戶之前已經(jīng)看過(guò)了,表示已經(jīng)有緩存了杜耙,按照正常的業(yè)務(wù)邏輯要顯示出來(lái)內(nèi)容搜骡,必須得在章節(jié)列表下載完成后,這顯然需要一定得等待(網(wǎng)絡(luò)請(qǐng)求)佑女,用戶體驗(yàn)肯定不好记靡。②用戶第二次打開(kāi)應(yīng)該顯示上次觀看得位置,如果按照正常邏輯顯示的是第一章第一頁(yè)团驱,這顯然也是不對(duì)的摸吠。

所以當(dāng)明確了有閱讀歷史(比如用戶就是從閱讀歷史啟動(dòng)的)時(shí),調(diào)用這個(gè)方法嚎花。

10.startFromCache(File cacheDir, String key, int chapterIndex, int charIndex, @NonNull String chapterName)

跟9類(lèi)似寸痢,這里多了一個(gè)參數(shù)File cacheDir,如果當(dāng)緩存的時(shí)候制定了自定義的路徑紊选,這里就需要傳入自定義的路徑啼止。如果沒(méi)有設(shè)置,默認(rèn)路徑是context.getCacheDir();

到這里兵罢,ReaderView#ReaderManager的API也介紹完了献烦。

核心類(lèi)其實(shí)也就3個(gè),ReaderView 卖词、ReaderView#Adapter巩那、ReaderView#ReaderManager,通過(guò)這三個(gè)類(lèi)可以完成閱讀器的基本功能了。后續(xù)再講解擴(kuò)展,以及原理拢操。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舶替,隨后出現(xiàn)的幾起案子令境,更是在濱河造成了極大的恐慌,老刑警劉巖顾瞪,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舔庶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡陈醒,警方通過(guò)查閱死者的電腦和手機(jī)惕橙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钉跷,“玉大人弥鹦,你說(shuō)我怎么就攤上這事∫蓿” “怎么了彬坏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)膝晾。 經(jīng)常有香客問(wèn)我栓始,道長(zhǎng),這世上最難降的妖魔是什么血当? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任幻赚,我火速辦了婚禮,結(jié)果婚禮上臊旭,老公的妹妹穿的比我還像新娘落恼。我一直安慰自己,他們只是感情好离熏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布领跛。 她就那樣靜靜地躺著,像睡著了一般撤奸。 火紅的嫁衣襯著肌膚如雪吠昭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天胧瓜,我揣著相機(jī)與錄音矢棚,去河邊找鬼。 笑死府喳,一個(gè)胖子當(dāng)著我的面吹牛蒲肋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼兜粘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼申窘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起孔轴,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剃法,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后路鹰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贷洲,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年晋柱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了优构。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雁竞,死狀恐怖钦椭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碑诉,我是刑警寧澤玉凯,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站联贩,受9級(jí)特大地震影響漫仆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泪幌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一盲厌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祸泪,春花似錦吗浩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至右蒲,卻和暖如春阀湿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瑰妄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工陷嘴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人间坐。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓灾挨,卻偏偏與公主長(zhǎng)得像邑退,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子劳澄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353