前言
看了整整一個(gè)月的FBReader的代碼及文章纺腊,終于搞懂了一點(diǎn)怎么使用FBReader,現(xiàn)在我向大家分享下怎么使用和修改FBReader梅桩,這個(gè)是面向小白的煮寡,畢竟當(dāng)初的我看這個(gè)閱讀器的時(shí)候也有點(diǎn)迷茫幸撕。第一次寫(xiě)簡(jiǎn)書(shū)律胀,寫(xiě)得不好請(qǐng)見(jiàn)諒炭菌。
在看怎么使用前黑低,先感謝 初見(jiàn)破曉 大佬的 FBReader 源碼閱讀筆記(一),里面給我們講解了部分FBReader的源代碼菩暗,推薦要用這個(gè)FBReader的大家先去看一看(雖然說(shuō)是大佬是講解了停团,但是代碼還是要自己看的佑稠,不要指望不讀源代碼就能了解怎么改)
修改的內(nèi)容
我的FBReader
是基于https://github.com/adolfAn/FBReader_AS修改而來(lái)的,做了以下修改
1辆琅、添加了菜單界面
2、添加了右側(cè)目錄界面
3似袁、添加了txt
目錄解析
4、合并了FBReader
的庫(kù)(未精簡(jiǎn))
經(jīng)測(cè)試著瓶,支持txt材原、epub、mobi
等格式威酒,至于pdf
卵慰,大家可以去看看MuPDF
或者PDFView
病线,整個(gè)FBReader
用的還是幾年前開(kāi)源的送挑,聽(tīng)說(shuō)現(xiàn)在的FBReader
已經(jīng)不開(kāi)源了,郁悶诫肠。
目錄
- 為自己的項(xiàng)目導(dǎo)入
FBReader
-
FBReader
的常識(shí) - 怎么使用
FBReaderHelper
- 自定義
FBReader
1栋豫、怎么為自己的項(xiàng)目導(dǎo)入FBReader
(1)首先挤安,先下載我的FBReader
項(xiàng)目,要加FBReader
庫(kù)丧鸯,肯定要知道項(xiàng)目庫(kù)到底能不能跑對(duì)吧蛤铜。 我的環(huán)境是:Android Studio 3.3.2
。
(2)如果能跑了,那就說(shuō)明你的開(kāi)發(fā)環(huán)境是可以的咯围肥,然后把整個(gè)fBReader
庫(kù)導(dǎo)入到你自己的項(xiàng)目中剿干,怎么導(dǎo)入呢,這就要大家自己自行百度了穆刻。
(3)添加讀寫(xiě)權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
(4)接著在自己的主項(xiàng)目中創(chuàng)建Application
類(lèi)文件差导,并在onCreate
中對(duì)FBReader
進(jìn)行初始化,并修改static
靜態(tài)域中的字段,填入你的 applicationId
。這里的 applicationId
是你的App項(xiàng)目(主項(xiàng)目)build.gradle
文件里的 applicationId
字段的值庄拇。如果 FBReaderIntents.DEFAULT_PACKAGE
字段不正確,結(jié)果會(huì)是打開(kāi)書(shū)本后什么都沒(méi)有我抠,一片空白。
public class App extends Application {
static {
//這里需要自己設(shè)置自己 build.gradle 里的 applicationId 到DEFAULT_PACKAGE字段
FBReaderIntents.DEFAULT_PACKAGE = "你應(yīng)用的applicationId";
}
@Override
public void onCreate() {
super.onCreate();
FBReaderApplication.init(this);
}
}
(5)初始化工作完成后逗宁,接著就是打開(kāi)書(shū)本了哼拔,參考提供的 MainActivity.java
文件僻孝,復(fù)制粘貼 onResume
和 onPause
函數(shù)中的操作作至你的項(xiàng)目Activity
中平酿,再賦值onClick
事件函數(shù)中的語(yǔ)句,該語(yǔ)句為打開(kāi)FBReader
的核心語(yǔ)句拍顷。
public class MainActivity extends AppCompatActivity {
private FBReaderHelper fbReaderHelper;
// private String path = Environment.getExternalStorageDirectory() + "/test.txt";
// private String path2 = Environment.getExternalStorageDirectory() + "/test.mobi";
private String path3 = Environment.getExternalStorageDirectory() + "/test.epub";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fbReaderHelper = new FBReaderHelper(this);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//必須確保activity有綁定服務(wù)才能通過(guò)jni獲取書(shū)本信息
fbReaderHelper.bindToService(new Runnable() {
@Override
public void run() {
Book book = fbReaderHelper.getCollection().getBookByFile(path3);
FBReader.openBook(MainActivity.this, book, null);
}
});
}
});
}
@Override
protected void onResume() {
super.onResume();
//對(duì)fbreader閱讀服務(wù)進(jìn)行綁定
fbReaderHelper.bindToService(null);
}
@Override
protected void onPause() {
//注銷(xiāo)fbreader閱讀服務(wù)綁定,service只允許綁定一個(gè)activity铃辖,所以為保證下一個(gè)activity能使用閱讀服務(wù)丰介,必須注銷(xiāo)
fbReaderHelper.unBind();
super.onPause();
}
}
(6)運(yùn)行
2柜某、FBReader的常識(shí)
在講使用 FBReaderHelper
前先補(bǔ)充下 FBReader
的常識(shí):
(1)FBReader
在你打開(kāi)了一本書(shū)后辞做,會(huì)緩存當(dāng)前的這本書(shū)课幕,盡管你關(guān)閉了FBReader
诞挨,由于靜態(tài)緩存扶认,書(shū)本仍然被保留下來(lái),所以你下次打開(kāi)書(shū)本將會(huì)是秒開(kāi)的,只有你打開(kāi)另一本書(shū)的時(shí)候書(shū)本緩存才會(huì)被覆蓋。
(2)FBReader
對(duì)書(shū)本的讀取需要通過(guò)JNI
交互 ,同時(shí)他的數(shù)據(jù)持久化存儲(chǔ)(存進(jìn)數(shù)據(jù)庫(kù))也通過(guò)JNI
屁使,所以看不懂書(shū)籍解析和數(shù)據(jù)存儲(chǔ)的并且看不懂C++
的朋友就有。。寞秃。扒腕,沒(méi)關(guān)系的竞慢,因?yàn)橹文啵乙部床欢?br>
(3)FBReader
對(duì)圖片和其他資源文件的讀取都是異步加載的筹煮,所以當(dāng)你要獲取圖片的時(shí)候肯定需要異步回調(diào)才能讀取到。
(4)FBReader
的中文排版可能有點(diǎn)丑居夹,因?yàn)樗⒉皇侵袊?guó)的败潦,所以你看中文的時(shí)候可能會(huì)覺(jué)得有點(diǎn)怪。
(5)FBReader
對(duì)文本的讀取和顯示是以段落劃分的准脂,FBReader
對(duì)字符的定位有3個(gè)參數(shù):
public final int ParagraphIndex; //段落索引
public final int ElementIndex; //詞的索引
public final int CharIndex; //字母的索引
由于英文單詞是由字母組成的劫扒,所有會(huì)出現(xiàn)字母的索引,但是我們中文是沒(méi)有的狸膏,所以當(dāng)你使用中文的時(shí)候粟关,字母的索引為 0 。FBReader
需要這3個(gè)參數(shù)才能定位到文本的位置环戈。
(6)FBReader
的事件機(jī)制信息傳遞是使用其自己建立的Action來(lái)實(shí)現(xiàn)的闷板,就是一種觀察者模式,他顯示通過(guò)綁定Action事件院塞,例如下面的顯示目錄事件遮晚,關(guān)于他在哪里發(fā)起事件的,大家可以追蹤下ActionCode.SHOW_TOC
字段拦止。
myFBReaderApp.addAction(ActionCode.SHOW_TOC, new ShowTOCAction(this, myFBReaderApp));
(7)FBReader
的架構(gòu)有點(diǎn)老县遣,可能看起來(lái)會(huì)有點(diǎn)吃力,但是其中也是有可以參考的代碼結(jié)構(gòu)的汹族。
(8)在使用FBReader
的時(shí)候如果你找不到某個(gè)功能在哪個(gè)代碼里萧求,我可以多用用AS的全局搜索快捷鍵,記得靈活使用顶瞒。
3夸政、怎么使用FBReaderHelper
說(shuō)了那么多,你只是教了我們?cè)趺磳?dǎo)入使用怎么還不給我們解釋FBReaderHelper
是啥榴徐,我?守问。別急,我現(xiàn)在講坑资,放下你的板凳耗帕。
FBReaderHelper
類(lèi)顧名思義,就是方便我們對(duì) FBReader
進(jìn)行操作的幫助類(lèi)袱贮,里面幾乎所有方法都是對(duì) FBReader
的操作仿便,但其作用不單止在于幫助我們操作,它還能幫助我們理解FBReader
的源碼攒巍,畢竟里面的方法就是從FBReader
里面找出來(lái)的嗽仪。
我把對(duì)FBReader
的操作理解為3個(gè)狀態(tài):未讀取、預(yù)讀取窑业、完全讀取钦幔。
未讀取:顧名思義
預(yù)讀取:就是只通過(guò) FBReader
獲取了書(shū)本的Book
類(lèi)和 BookModel
類(lèi),但是沒(méi)有打開(kāi)該書(shū)本常柄,沒(méi)有進(jìn)入到讀書(shū)界面鲤氢。
完全讀取:說(shuō)多了就是,把整本書(shū)打開(kāi)了西潘,連書(shū)本有什么字卷玉,有什么圖片都顯示了出來(lái)了。
(1)像設(shè)置字體大小這種閱讀配置能在3種狀態(tài)下進(jìn)行喷市,因?yàn)樵撛O(shè)置并不一定需要 Book
相种、BookModel
。
/**
* 設(shè)置字體大小
*/
public void setFontSize(int size) {
myFBReaderApp.ViewOptions.getTextStyleCollection().getBaseStyle().FontSizeOption.setValue(size);
if (myFBReaderApp.getViewWidget() != null) {
myFBReaderApp.clearTextCaches();
myFBReaderApp.getViewWidget().repaint();
}
}
(2)但是像獲取書(shū)本總字?jǐn)?shù)這種就必須先讓Activity
綁定FBReader
的 service
才能使用了品姓,因?yàn)闀?shū)本的預(yù)讀取是需要通過(guò) Service
和 JNI
交互的寝并,從service
處獲取到書(shū)本的BookModel
后才能獲取書(shū)本的總字?jǐn)?shù)和段落數(shù)棺禾。
/**
* 獲取書(shū)本總字?jǐn)?shù) 應(yīng)該先確定Collection是否已經(jīng)綁定服務(wù)且已打開(kāi)書(shū)本(完全讀取)
*
* @return
*/
public int getSumTextCount() {
if (myFBReaderApp.Model == null) return 0;
else {
return myFBReaderApp.Model.getTextModel().getTextLength(
myFBReaderApp.Model.getTextModel().getParagraphsNumber()
);
}
}
//也可以這樣實(shí)現(xiàn)(傳入一個(gè)Book對(duì)象進(jìn)行預(yù)讀取獲取BookModel從而獲取字體總數(shù))(預(yù)讀忍诽啊)
public int getSumTextCount(Book book) {
BookModel bookModel = createBookModel(book);
return bookModel.getTextModel().getTextLength(
bookModel.getTextModel().getParagraphsNumber()
);
}
兩者的出來(lái)的結(jié)果都是一樣的隆圆,區(qū)別在于,一個(gè)是完全讀取書(shū)籍镀岛,一個(gè)是預(yù)讀取弦牡。
(3)完全讀取需要FBReader
打開(kāi)整本書(shū)籍,走完打開(kāi)書(shū)本的流程漂羊,這樣可以獲取到書(shū)本的所有信息驾锰,包括文字圖片,例如獲取當(dāng)前頁(yè)字?jǐn)?shù)就需要讀取書(shū)籍并顯示后才能知道當(dāng)前頁(yè)的字?jǐn)?shù)走越。
/**
* 獲取當(dāng)前頁(yè)的字?jǐn)?shù)量 應(yīng)該先確定Collection是否已經(jīng)綁定服務(wù)且已打開(kāi)書(shū)本
*
* @return
*/
public int getCurPageWordCount() {
ZLTextWordCursor stCursor = getStartCursor();
ZLTextWordCursor edCursor = getEndCursor();
if (myFBReaderApp.Model == null || edCursor.getParagraphIndex() <= 0)
return edCursor.getElementIndex();
return myFBReaderApp.Model.getTextModel().getTextLength(edCursor.getParagraphIndex() - 1) + edCursor.getElementIndex() -
myFBReaderApp.Model.getTextModel().getTextLength(stCursor.getParagraphIndex()) + stCursor.getElementIndex();
}
(4)圖片的加載椭豫,在FBReader
中圖片的加載時(shí)異步的,所以當(dāng)你需要獲取圖片的時(shí)候肯定是需要回調(diào)的买喧,例如封面圖的獲取捻悯。
/**
* 異步加載封面圖
*
* @param book FBReader 的 Book 對(duì)象
* @param listener
*/
public void loadBookCover(@NonNull Book book, @NonNull final OnGetCoverListener listener) {
PluginCollection pluginCollection = PluginCollection.Instance(Paths.systemInfo(activity));
BookUtil.getEncoding(book, pluginCollection);
final ZLImage image = CoverUtil.getCover(book, pluginCollection);
if (image == null) {
listener.finish(false, null);
} else {
if (image instanceof ZLImageProxy) {
((ZLImageProxy) image).startSynchronization(new AndroidImageSynchronizer(activity), new Runnable() {
public void run() {
loadCover(image, listener);
}
});
} else {
loadCover(image, listener);
}
}
}
(5)是否需要預(yù)讀取或完全讀取呢,我已經(jīng)在 FBReaderHelper
的函數(shù)上寫(xiě)上注釋了淤毛,大家在使用的時(shí)候記得區(qū)分下今缚,要不然可能會(huì)報(bào)空指針或者獲取到的信息是上一本打開(kāi)過(guò)的書(shū)籍的。
(6)可能FBReaderHelper
會(huì)有點(diǎn)坑低淡,例如空指針的問(wèn)題姓言,這個(gè)嘛是因?yàn)槲耶?dāng)時(shí)寫(xiě)出來(lái)只是為了記錄操作方法,如果說(shuō)所獲取到的 myFBReaderApp.Model
為空蔗蹋,你或許需要自己調(diào)用 FBReaderHelper.createBookModel()
來(lái)代替何荚。
4、自定義FBReader
- 修改圖片顯示的配置
- 自定義圖片的打開(kāi)方式
- 自定義底部時(shí)間欄的顯示
- 翻頁(yè)狀態(tài)的監(jiān)聽(tīng)
- 設(shè)置默認(rèn)配置
- 攔截跳轉(zhuǎn)到下一章
4.1. 修改圖片顯示的配置
圖片配置的文件在org.geometerplus.fbreader.fbreader.options.ImageOptions.java
文件中猪杭。
public ImageOptions() {
ImageViewBackground =
new ZLColorOption("Colors", "ImageViewBackground", new ZLColor(255, 255, 255));
FitToScreen =
new ZLEnumOption<FBView.ImageFitting>("Options", "FitImagesToScreen", FBView.ImageFitting.covers);
TapAction =
new ZLEnumOption<TapActionEnum>("Options", "ImageTappingAction", TapActionEnum.openImageView);
MatchBackground =
new ZLBooleanOption("Colors", "ImageMatchBackground", false);
}
從上往下數(shù)餐塘,四個(gè)對(duì)象分別控制的是圖片的背景色,圖片的填充縮放類(lèi)型皂吮,圖片點(diǎn)擊后的行為戒傻,圖片的遮罩效果。
主要講的是第二個(gè)對(duì)象蜂筹,如果設(shè)置的值是FBView.ImageFitting.covers
需纳,則是只有封面圖填充整個(gè)閱讀器可視范圍。如果設(shè)置的是FBView.ImageFitting.all
則是所有圖都填充整個(gè)閱讀器可視范圍艺挪。
如果想實(shí)現(xiàn)點(diǎn)擊圖片后不對(duì)圖片處理或打開(kāi)可以將TapActionEnum.openImageView
換成TapActionEnum.doNothing
不翩。
4.2. 自定義圖片的打開(kāi)方式
圖片視圖是一個(gè)Activity
界面,叫org.geometerplus.android.fbreader.image.ImageViewActivity.java
,當(dāng)在閱讀界面點(diǎn)擊圖片時(shí)口蝠,會(huì)調(diào)用打開(kāi)該Activity的代碼
} else if (soul instanceof ZLTextImageRegionSoul) {
Reader.getTextView().hideOutline();
Reader.getViewWidget().repaint();
final String url = ((ZLTextImageRegionSoul) soul).ImageElement.URL;
if (url != null) {
try {
final Intent intent = new Intent();
intent.setClass(BaseActivity,ImageViewActivity.class);
intent.putExtra(ImageViewActivity.URL_KEY, url);
intent.putExtra(
ImageViewActivity.BACKGROUND_COLOR_KEY,
Reader.ImageOptions.ImageViewBackground.getValue().intValue()
);
OrientationUtil.startActivity(BaseActivity, intent);
BaseActivity.overridePendingTransition(R.anim.activity_anim_no, R.anim.activity_anim_no);
} catch (Exception e) {
e.printStackTrace();
}
}
}
只需要修改參考Activity
里面的代碼編寫(xiě)即可器钟。
4.3. 自定義底部時(shí)間欄的顯示
FBReader
默認(rèn)時(shí)間欄和閱讀進(jìn)度顯示在底部的,如果需要顯示在上方需要修改改動(dòng)的地方比較多亚皂,這里只介紹下如何修改底部欄顯示的樣式俱箱。
控制底部欄顯示的具體代碼在 org.geometerplus.fbreader.fbreader.FBView.java
文件中,你需要做的是修改其大概位于642行的FooterNewStyle
類(lèi)中的paint()
函數(shù)灭必。相信看到這個(gè)函數(shù)內(nèi)容的人心里或多或少都會(huì)有些B數(shù),知道該怎么修改了乃摹,在這里我貼下代碼禁漓。
private class FooterNewStyle extends Footer {
public synchronized void paint(ZLPaintContext context) {
final FooterOptions footerOptions = myViewOptions.getFooterOptions();
final ColorProfile cProfile = myViewOptions.getColorProfile();
context.clear(cProfile.FooterNGBackgroundOption.getValue());
context.clear(getBackgroundColor());
final BookModel model = myReader.Model;
if (model == null) return;
final int left = getLeftMargin();
final int right = context.getWidth() - getRightMargin();
final int height = getHeight();
final int charHeight = setFont(context, height / 2, false);
final PagePosition pagePosition = FBView.this.pagePosition();
context.setTextColor(cProfile.RegularTextOption.getValue());
if (footerOptions.ShowBattery.getValue()) {
context.drawString(left, (height + charHeight + 1) / 2, "電池 " + myReader.getBatteryLevel() + "%");
}
if (footerOptions.showProgressAsPages()) {
String str = pagePosition.Current + "/" + pagePosition.Total;
int strWidth = context.getStringWidth(str);
context.drawString((right - strWidth+left) / 2 , (height + charHeight + 1) / 2, str);
}
if (footerOptions.showProgressAsPercentage() && pagePosition.Total != 0) {
String str = pagePosition.Current + "/" + pagePosition.Total + " " + 100 * pagePosition.Current / pagePosition.Total + "%";
int strWidth = context.getStringWidth(str);
context.drawString((right - strWidth+left) / 2 , (height + charHeight + 1) / 2, str);
}
if (footerOptions.ShowClock.getValue()) {
String str = ZLibrary.Instance().getCurrentTimeString();
int strWidth = context.getStringWidth(str);
context.drawString(right - strWidth, (height + charHeight + 1) / 2, str);
}
}
}
實(shí)際起效的代碼在21行以后,是不是很像我們用過(guò)的canvas
畫(huà)板和paint
畫(huà)筆孵睬?
4.4. 翻頁(yè)狀態(tài)的監(jiān)聽(tīng)
我在org.geometerplus.fbreader.fbreader
包下添加了3個(gè)Action
播歼,分別是PageTurnStartAction
、PageTurningAction
掰读、PageTurnEndAction
秘狞,如果FBReader
發(fā)生了翻頁(yè),那么會(huì)分別順序執(zhí)行三個(gè)Action蹈集,如果你想計(jì)算翻頁(yè)后的頁(yè)面的字體數(shù)量烁试,那么可以在PageTurnEndAction
的run()
函數(shù)中編寫(xiě)獲取字?jǐn)?shù)的代碼。注意:沒(méi)有翻頁(yè)動(dòng)畫(huà)的時(shí)候是不會(huì)執(zhí)行PageTurningAction
的拢肆。
4.5. 設(shè)置默認(rèn)配置
在org.geometerplus.zlibrary.ui.android.library.ZLAndroidLibrary.java
中保存著一些應(yīng)用級(jí)別的設(shè)置减响。
public final ZLBooleanOption ShowStatusBarOption = new ZLBooleanOption("LookNFeel", "ShowStatusBar", false);
public final ZLBooleanOption OldShowActionBarOption = new ZLBooleanOption("LookNFeel", "ShowActionBar", false);
public final ZLBooleanOption ShowActionBarOption = new ZLBooleanOption("LookNFeel", "ShowActionBarNew", false);
public final ZLBooleanOption EnableFullscreenModeOption = new ZLBooleanOption("LookNFeel", "FullscreenMode", true);
public final ZLBooleanOption DisableButtonLightsOption = new ZLBooleanOption("LookNFeel", "DisableButtonLights", !DeviceType.Instance().hasButtonLightsBug());
public final ZLIntegerRangeOption BatteryLevelToTurnScreenOffOption = new ZLIntegerRangeOption("LookNFeel", "BatteryLevelToTurnScreenOff", 0, 100, 50);
public final ZLBooleanOption DontTurnScreenOffDuringChargingOption = new ZLBooleanOption("LookNFeel", "DontTurnScreenOffDuringCharging", true);
public final ZLIntegerRangeOption ScreenBrightnessLevelOption = new ZLIntegerRangeOption("LookNFeel", "ScreenBrightnessLevel", 0, 100, 0);
這些英文都是比較好看懂的,如果不懂得可以google下郭怪。
4.6. 攔截跳轉(zhuǎn)到下一章
對(duì)于FBReader_AS
的源碼我稍微修改了下支示,把PageTurnAction
翻頁(yè)行為的注冊(cè)挪了下位置,并在里面添加了翻頁(yè)攔截鄙才,當(dāng)每進(jìn)行翻頁(yè)時(shí)會(huì)執(zhí)行FBReader.java
文件最底部的onTurnBackIntercept
方法
public boolean onTurnBackIntercept(boolean isForward) {
//判斷是否是準(zhǔn)備翻到下一頁(yè)的并且當(dāng)前頁(yè)是該章節(jié)的最后一頁(yè)
if (isForward && myFBReaderApp.BookTextView.getEndCursor().getParagraphCursor().isEndOfSection()) {
//TODO 準(zhǔn)備翻到下一章的攔截如果需要攔截返回true
return false;
}
return false;
}
這個(gè)函數(shù)傳入一個(gè)參數(shù)isForward
颂鸿,如果為true
,就是往右翻頁(yè),false
就為左翻頁(yè)攒庵。
該函數(shù)的返回值的類(lèi)似于View
中的onTouchEvent
嘴纺,如果返回值為true
,閱讀器就不會(huì)進(jìn)行翻頁(yè),大家可以在return
前做自己的事情叙甸,例如彈出一個(gè)請(qǐng)求框之類(lèi)的颖医,如果返回值為false
,閱讀器就會(huì)正常的翻頁(yè)裆蒸,不停止翻頁(yè)熔萧。
先寫(xiě)著這么多吧,其實(shí)說(shuō)多了,如果要改fbreader佛致,真的必須看懂源碼贮缕,這個(gè)文章就當(dāng)作給寫(xiě)引子吧,靈活一點(diǎn)俺榆,多用用ctrl+左鍵和全局搜索感昼,幾乎都能找到你想要找的東西。