*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布
先看擁有部分功能的效果圖套么。
最近小說(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)迫不及待的想看一下效果了
僅僅需要這幾步我們就實(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ò)展,以及原理拢操。