前言
看了整整一個(gè)月的FBReader的代碼及文章,終于搞懂了一點(diǎn)怎么使用FBReader,現(xiàn)在我向大家分享下怎么使用和修改FBReader,這個(gè)是面向小白的,畢竟當(dāng)初的我看這個(gè)閱讀器的時(shí)候也有點(diǎn)迷茫。第一次寫簡(jiǎn)書,寫得不好請(qǐng)見諒。
在看怎么使用前,先感謝 初見破曉 大佬的 FBReader 源碼閱讀筆記(一),里面給我們講解了部分FBReader的源代碼,推薦要用這個(gè)FBReader的大家先去看一看(雖然說是大佬是講解了,但是代碼還是要自己看的,不要指望不讀源代碼就能了解怎么改)
修改的內(nèi)容
我的FBReader是基于https://github.com/adolfAn/FBReader_AS修改而來的,做了以下修改
1、添加了菜單界面
2、添加了右側(cè)目錄界面
3、添加了txt目錄解析
4、合并了FBReader的庫(未精簡(jiǎn))
經(jīng)測(cè)試,支持txt、epub、mobi等格式,至于pdf,大家可以去看看MuPDF或者PDFView,整個(gè)FBReader用的還是幾年前開源的,聽說現(xiàn)在的FBReader已經(jīng)不開源了,郁悶。
目錄
- 為自己的項(xiàng)目導(dǎo)入
FBReader -
FBReader的常識(shí) - 怎么使用
FBReaderHelper - 自定義
FBReader
1、怎么為自己的項(xiàng)目導(dǎo)入FBReader
(1)首先,先下載我的FBReader項(xiàng)目,要加FBReader庫,肯定要知道項(xiàng)目庫到底能不能跑對(duì)吧。 我的環(huán)境是:Android Studio 3.3.2 。
(2)如果能跑了,那就說明你的開發(fā)環(huán)境是可以的咯,然后把整個(gè)fBReader庫導(dǎo)入到你自己的項(xiàng)目中,怎么導(dǎo)入呢,這就要大家自己自行百度了。
(3)添加讀寫權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
(4)接著在自己的主項(xiàng)目中創(chuàng)建Application類文件,并在onCreate中對(duì)FBReader進(jìn)行初始化,并修改static靜態(tài)域中的字段,填入你的 applicationId。這里的 applicationId 是你的App項(xiàng)目(主項(xiàng)目)build.gradle文件里的 applicationId 字段的值。如果 FBReaderIntents.DEFAULT_PACKAGE 字段不正確,結(jié)果會(huì)是打開書本后什么都沒有,一片空白。
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)初始化工作完成后,接著就是打開書本了,參考提供的 MainActivity.java 文件,復(fù)制粘貼 onResume 和 onPause 函數(shù)中的操作作至你的項(xiàng)目Activity中,再賦值onClick事件函數(shù)中的語句,該語句為打開FBReader的核心語句。
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ù)才能通過jni獲取書本信息
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() {
//注銷fbreader閱讀服務(wù)綁定,service只允許綁定一個(gè)activity,所以為保證下一個(gè)activity能使用閱讀服務(wù),必須注銷
fbReaderHelper.unBind();
super.onPause();
}
}
(6)運(yùn)行
2、FBReader的常識(shí)
在講使用 FBReaderHelper 前先補(bǔ)充下 FBReader 的常識(shí):
(1)FBReader在你打開了一本書后,會(huì)緩存當(dāng)前的這本書,盡管你關(guān)閉了FBReader,由于靜態(tài)緩存,書本仍然被保留下來,所以你下次打開書本將會(huì)是秒開的,只有你打開另一本書的時(shí)候書本緩存才會(huì)被覆蓋。
(2)FBReader對(duì)書本的讀取需要通過JNI交互 ,同時(shí)他的數(shù)據(jù)持久化存儲(chǔ)(存進(jìn)數(shù)據(jù)庫)也通過JNI,所以看不懂書籍解析和數(shù)據(jù)存儲(chǔ)的并且看不懂C++的朋友就有。。。,沒關(guān)系的,因?yàn)?,我也看不懂?br>
(3)FBReader對(duì)圖片和其他資源文件的讀取都是異步加載的,所以當(dāng)你要獲取圖片的時(shí)候肯定需要異步回調(diào)才能讀取到。
(4)FBReader的中文排版可能有點(diǎn)丑,因?yàn)樗⒉皇侵袊?guó)的,所以你看中文的時(shí)候可能會(huì)覺得有點(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)字母的索引,但是我們中文是沒有的,所以當(dāng)你使用中文的時(shí)候,字母的索引為 0 。FBReader需要這3個(gè)參數(shù)才能定位到文本的位置。
(6)FBReader的事件機(jī)制信息傳遞是使用其自己建立的Action來實(shí)現(xiàn)的,就是一種觀察者模式,他顯示通過綁定Action事件,例如下面的顯示目錄事件,關(guān)于他在哪里發(fā)起事件的,大家可以追蹤下ActionCode.SHOW_TOC字段。
myFBReaderApp.addAction(ActionCode.SHOW_TOC, new ShowTOCAction(this, myFBReaderApp));
(7)FBReader的架構(gòu)有點(diǎn)老,可能看起來會(huì)有點(diǎn)吃力,但是其中也是有可以參考的代碼結(jié)構(gòu)的。
(8)在使用FBReader的時(shí)候如果你找不到某個(gè)功能在哪個(gè)代碼里,我可以多用用AS的全局搜索快捷鍵,記得靈活使用。
3、怎么使用FBReaderHelper
說了那么多,你只是教了我們?cè)趺磳?dǎo)入使用怎么還不給我們解釋FBReaderHelper是啥,我?。別急,我現(xiàn)在講,放下你的板凳。
FBReaderHelper 類顧名思義,就是方便我們對(duì) FBReader進(jìn)行操作的幫助類,里面幾乎所有方法都是對(duì) FBReader的操作,但其作用不單止在于幫助我們操作,它還能幫助我們理解FBReader的源碼,畢竟里面的方法就是從FBReader里面找出來的。
我把對(duì)FBReader的操作理解為3個(gè)狀態(tài):未讀取、預(yù)讀取、完全讀取。
未讀取:顧名思義
預(yù)讀取:就是只通過 FBReader獲取了書本的Book類和 BookModel類,但是沒有打開該書本,沒有進(jìn)入到讀書界面。
完全讀取:說多了就是,把整本書打開了,連書本有什么字,有什么圖片都顯示了出來了。
(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)但是像獲取書本總字?jǐn)?shù)這種就必須先讓Activity綁定FBReader 的 service才能使用了,因?yàn)闀镜念A(yù)讀取是需要通過 Service 和 JNI交互的,從service 處獲取到書本的BookModel 后才能獲取書本的總字?jǐn)?shù)和段落數(shù)。
/**
* 獲取書本總字?jǐn)?shù) 應(yīng)該先確定Collection是否已經(jīng)綁定服務(wù)且已打開書本(完全讀取)
*
* @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()
);
}
兩者的出來的結(jié)果都是一樣的,區(qū)別在于,一個(gè)是完全讀取書籍,一個(gè)是預(yù)讀取。
(3)完全讀取需要FBReader打開整本書籍,走完打開書本的流程,這樣可以獲取到書本的所有信息,包括文字圖片,例如獲取當(dāng)前頁字?jǐn)?shù)就需要讀取書籍并顯示后才能知道當(dāng)前頁的字?jǐn)?shù)。
/**
* 獲取當(dāng)前頁的字?jǐn)?shù)量 應(yīng)該先確定Collection是否已經(jīng)綁定服務(wù)且已打開書本
*
* @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ù)上寫上注釋了,大家在使用的時(shí)候記得區(qū)分下,要不然可能會(huì)報(bào)空指針或者獲取到的信息是上一本打開過的書籍的。
(6)可能FBReaderHelper會(huì)有點(diǎn)坑,例如空指針的問題,這個(gè)嘛是因?yàn)槲耶?dāng)時(shí)寫出來只是為了記錄操作方法,如果說所獲取到的 myFBReaderApp.Model 為空,你或許需要自己調(diào)用 FBReaderHelper.createBookModel()來代替。
4、自定義FBReader
- 修改圖片顯示的配置
- 自定義圖片的打開方式
- 自定義底部時(shí)間欄的顯示
- 翻頁狀態(tài)的監(jiān)聽
- 設(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ì)象分別控制的是圖片的背景色,圖片的填充縮放類型,圖片點(diǎn)擊后的行為,圖片的遮罩效果。
主要講的是第二個(gè)對(duì)象,如果設(shè)置的值是FBView.ImageFitting.covers,則是只有封面圖填充整個(gè)閱讀器可視范圍。如果設(shè)置的是FBView.ImageFitting.all則是所有圖都填充整個(gè)閱讀器可視范圍。
如果想實(shí)現(xiàn)點(diǎn)擊圖片后不對(duì)圖片處理或打開可以將TapActionEnum.openImageView換成TapActionEnum.doNothing。
4.2. 自定義圖片的打開方式
圖片視圖是一個(gè)Activity界面,叫org.geometerplus.android.fbreader.image.ImageViewActivity.java,當(dāng)在閱讀界面點(diǎn)擊圖片時(shí),會(huì)調(diào)用打開該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里面的代碼編寫即可。
4.3. 自定義底部時(shí)間欄的顯示
FBReader默認(rèn)時(shí)間欄和閱讀進(jìn)度顯示在底部的,如果需要顯示在上方需要修改改動(dòng)的地方比較多,這里只介紹下如何修改底部欄顯示的樣式。
控制底部欄顯示的具體代碼在 org.geometerplus.fbreader.fbreader.FBView.java文件中,你需要做的是修改其大概位于642行的FooterNewStyle類中的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行以后,是不是很像我們用過的canvas畫板和paint畫筆?
4.4. 翻頁狀態(tài)的監(jiān)聽
我在org.geometerplus.fbreader.fbreader包下添加了3個(gè)Action,分別是PageTurnStartAction、PageTurningAction、PageTurnEndAction,如果FBReader發(fā)生了翻頁,那么會(huì)分別順序執(zhí)行三個(gè)Action,如果你想計(jì)算翻頁后的頁面的字體數(shù)量,那么可以在PageTurnEndAction的run()函數(shù)中編寫獲取字?jǐn)?shù)的代碼。注意:沒有翻頁動(dòng)畫的時(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翻頁行為的注冊(cè)挪了下位置,并在里面添加了翻頁攔截,當(dāng)每進(jìn)行翻頁時(shí)會(huì)執(zhí)行FBReader.java文件最底部的onTurnBackIntercept方法
public boolean onTurnBackIntercept(boolean isForward) {
//判斷是否是準(zhǔn)備翻到下一頁的并且當(dāng)前頁是該章節(jié)的最后一頁
if (isForward && myFBReaderApp.BookTextView.getEndCursor().getParagraphCursor().isEndOfSection()) {
//TODO 準(zhǔn)備翻到下一章的攔截如果需要攔截返回true
return false;
}
return false;
}
這個(gè)函數(shù)傳入一個(gè)參數(shù)isForward,如果為true,就是往右翻頁,false就為左翻頁。
該函數(shù)的返回值的類似于View中的onTouchEvent,如果返回值為true,閱讀器就不會(huì)進(jìn)行翻頁,大家可以在return前做自己的事情,例如彈出一個(gè)請(qǐng)求框之類的,如果返回值為false,閱讀器就會(huì)正常的翻頁,不停止翻頁。
先寫著這么多吧,其實(shí)說多了,如果要改fbreader,真的必須看懂源碼,這個(gè)文章就當(dāng)作給寫引子吧,靈活一點(diǎn),多用用ctrl+左鍵和全局搜索,幾乎都能找到你想要找的東西。