FBReader的导入及使用

前言

看了整整一个月的FBReader的代码及文章,终于搞懂了一点怎么使用FBReader,现在我向大家分享下怎么使用和修改FBReader,这个是面向小白的,毕竟当初的我看这个阅读器的时候也有点迷茫。第一次写简书,写得不好请见谅。

在看怎么使用前,先感谢 初见破晓 大佬的 FBReader 源码阅读笔记(一),里面给我们讲解了部分FBReader的源代码,推荐要用这个FBReader的大家先去看一看(虽然说是大佬是讲解了,但是代码还是要自己看的,不要指望不读源代码就能了解怎么改)

修改的内容

我的FBReader是基于https://github.com/adolfAn/FBReader_AS修改而来的,做了以下修改
1、添加了菜单界面
2、添加了右侧目录界面
3、添加了txt目录解析
4、合并了FBReader的库(未精简)

经测试,支持txt、epub、mobi等格式,至于pdf,大家可以去看看MuPDF或者PDFView,整个FBReader用的还是几年前开源的,听说现在的FBReader已经不开源了,郁闷。

目录

  1. 为自己的项目导入FBReader
  2. FBReader的常识
  3. 怎么使用FBReaderHelper
  4. 自定义FBReader

1、怎么为自己的项目导入FBReader

我的 FBReader GitHub项目地址

(1)首先,先下载我的FBReader项目,要加FBReader库,肯定要知道项目库到底能不能跑对吧。 我的环境是:Android Studio 3.3.2
(2)如果能跑了,那就说明你的开发环境是可以的咯,然后把整个fBReader库导入到你自己的项目中,怎么导入呢,这就要大家自己自行百度了。
(3)添加读写权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

(4)接着在自己的主项目中创建Application类文件,并在onCreate中对FBReader进行初始化,并修改static静态域中的字段,填入你的 applicationId。这里的 applicationId 是你的App项目(主项目)build.gradle文件里的 applicationId 字段的值。如果 FBReaderIntents.DEFAULT_PACKAGE 字段不正确,结果会是打开书本后什么都没有,一片空白。

public class App extends Application {

    static {
        //这里需要自己设置自己 build.gradle 里的  applicationId 到DEFAULT_PACKAGE字段
        FBReaderIntents.DEFAULT_PACKAGE = "你应用的applicationId";
    }

    @Override
    public void onCreate() {
        super.onCreate();
        FBReaderApplication.init(this);
    }
}

(5)初始化工作完成后,接着就是打开书本了,参考提供的 MainActivity.java 文件,复制粘贴 onResumeonPause 函数中的操作作至你的项目Activity中,再赋值onClick事件函数中的语句,该语句为打开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有绑定服务才能通过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();
        //对fbreader阅读服务进行绑定
        fbReaderHelper.bindToService(null);
    }

    @Override
    protected void onPause() {
        //注销fbreader阅读服务绑定,service只允许绑定一个activity,所以为保证下一个activity能使用阅读服务,必须注销
        fbReaderHelper.unBind();
        super.onPause();
    }
}

(6)运行

2、FBReader的常识

在讲使用 FBReaderHelper 前先补充下 FBReader 的常识:
(1)FBReader在你打开了一本书后,会缓存当前的这本书,尽管你关闭了FBReader,由于静态缓存,书本仍然被保留下来,所以你下次打开书本将会是秒开的,只有你打开另一本书的时候书本缓存才会被覆盖。
(2)FBReader对书本的读取需要通过JNI交互 ,同时他的数据持久化存储(存进数据库)也通过JNI,所以看不懂书籍解析和数据存储的并且看不懂C++的朋友就有。。。,没关系的,因为,我也看不懂。
(3)FBReader对图片和其他资源文件的读取都是异步加载的,所以当你要获取图片的时候肯定需要异步回调才能读取到。
(4)FBReader的中文排版可能有点丑,因为它并不是中国的,所以你看中文的时候可能会觉得有点怪。
(5)FBReader对文本的读取和显示是以段落划分的,FBReader对字符的定位有3个参数:

    public final int ParagraphIndex;    //段落索引
    public final int ElementIndex;      //词的索引
    public final int CharIndex;         //字母的索引

由于英文单词是由字母组成的,所有会出现字母的索引,但是我们中文是没有的,所以当你使用中文的时候,字母的索引为 0 。FBReader需要这3个参数才能定位到文本的位置。
(6)FBReader的事件机制信息传递是使用其自己建立的Action来实现的,就是一种观察者模式,他显示通过绑定Action事件,例如下面的显示目录事件,关于他在哪里发起事件的,大家可以追踪下ActionCode.SHOW_TOC字段。

myFBReaderApp.addAction(ActionCode.SHOW_TOC, new ShowTOCAction(this, myFBReaderApp));

(7)FBReader的架构有点老,可能看起来会有点吃力,但是其中也是有可以参考的代码结构的。
(8)在使用FBReader的时候如果你找不到某个功能在哪个代码里,我可以多用用AS的全局搜索快捷键,记得灵活使用。

3、怎么使用FBReaderHelper

说了那么多,你只是教了我们怎么导入使用怎么还不给我们解释FBReaderHelper是啥,我™。别急,我现在讲,放下你的板凳。

FBReaderHelper 类顾名思义,就是方便我们对 FBReader进行操作的帮助类,里面几乎所有方法都是对 FBReader的操作,但其作用不单止在于帮助我们操作,它还能帮助我们理解FBReader的源码,毕竟里面的方法就是从FBReader里面找出来的。

我把对FBReader的操作理解为3个状态:未读取、预读取、完全读取。
未读取:顾名思义
预读取:就是只通过 FBReader获取了书本的Book类和 BookModel类,但是没有打开该书本,没有进入到读书界面。
完全读取:说多了就是,把整本书打开了,连书本有什么字,有什么图片都显示了出来了。

(1)像设置字体大小这种阅读配置能在3种状态下进行,因为该设置并不一定需要 BookBookModel

     /**
     * 设置字体大小
     */
    public void setFontSize(int size) {
        myFBReaderApp.ViewOptions.getTextStyleCollection().getBaseStyle().FontSizeOption.setValue(size);
        if (myFBReaderApp.getViewWidget() != null) {
            myFBReaderApp.clearTextCaches();
            myFBReaderApp.getViewWidget().repaint();
        }
    }

(2)但是像获取书本总字数这种就必须先让Activity绑定FBReaderservice才能使用了,因为书本的预读取是需要通过 ServiceJNI交互的,从service 处获取到书本的BookModel 后才能获取书本的总字数和段落数。

    /**
     * 获取书本总字数  应该先确定Collection是否已经绑定服务且已打开书本(完全读取)
     *
     * @return
     */
    public int getSumTextCount() {
        if (myFBReaderApp.Model == null) return 0;
        else {
            return myFBReaderApp.Model.getTextModel().getTextLength(
                    myFBReaderApp.Model.getTextModel().getParagraphsNumber()
            );
        }
    }
    //也可以这样实现(传入一个Book对象进行预读取获取BookModel从而获取字体总数)(预读取)
    public int getSumTextCount(Book book) {
        BookModel bookModel = createBookModel(book);
        return bookModel.getTextModel().getTextLength(
                bookModel.getTextModel().getParagraphsNumber()
        );
    }

两者的出来的结果都是一样的,区别在于,一个是完全读取书籍,一个是预读取。

(3)完全读取需要FBReader打开整本书籍,走完打开书本的流程,这样可以获取到书本的所有信息,包括文字图片,例如获取当前页字数就需要读取书籍并显示后才能知道当前页的字数。

    /**
     * 获取当前页的字数量   应该先确定Collection是否已经绑定服务且已打开书本
     *
     * @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中图片的加载时异步的,所以当你需要获取图片的时候肯定是需要回调的,例如封面图的获取。

    /**
     * 异步加载封面图
     *
     * @param book   FBReader 的 Book 对象
     * @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)是否需要预读取或完全读取呢,我已经在 FBReaderHelper 的函数上写上注释了,大家在使用的时候记得区分下,要不然可能会报空指针或者获取到的信息是上一本打开过的书籍的。
(6)可能FBReaderHelper会有点坑,例如空指针的问题,这个嘛是因为我当时写出来只是为了记录操作方法,如果说所获取到的 myFBReaderApp.Model 为空,你或许需要自己调用 FBReaderHelper.createBookModel()来代替。

4、自定义FBReader

  1. 修改图片显示的配置
  2. 自定义图片的打开方式
  3. 自定义底部时间栏的显示
  4. 翻页状态的监听
  5. 设置默认配置
  6. 拦截跳转到下一章
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);
    }

从上往下数,四个对象分别控制的是图片的背景色,图片的填充缩放类型,图片点击后的行为,图片的遮罩效果。
主要讲的是第二个对象,如果设置的值是FBView.ImageFitting.covers,则是只有封面图填充整个阅读器可视范围。如果设置的是FBView.ImageFitting.all则是所有图都填充整个阅读器可视范围。
如果想实现点击图片后不对图片处理或打开可以将TapActionEnum.openImageView换成TapActionEnum.doNothing

4.2. 自定义图片的打开方式

图片视图是一个Activity界面,叫org.geometerplus.android.fbreader.image.ImageViewActivity.java,当在阅读界面点击图片时,会调用打开该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. 自定义底部时间栏的显示

FBReader默认时间栏和阅读进度显示在底部的,如果需要显示在上方需要修改改动的地方比较多,这里只介绍下如何修改底部栏显示的样式。
控制底部栏显示的具体代码在 org.geometerplus.fbreader.fbreader.FBView.java文件中,你需要做的是修改其大概位于642行的FooterNewStyle类中的paint()函数。相信看到这个函数内容的人心里或多或少都会有些B数,知道该怎么修改了,在这里我贴下代码。

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);
            }

        }
    }

实际起效的代码在21行以后,是不是很像我们用过的canvas画板和paint画笔?

4.4. 翻页状态的监听

我在org.geometerplus.fbreader.fbreader包下添加了3个Action,分别是PageTurnStartActionPageTurningActionPageTurnEndAction,如果FBReader发生了翻页,那么会分别顺序执行三个Action,如果你想计算翻页后的页面的字体数量,那么可以在PageTurnEndActionrun()函数中编写获取字数的代码。注意:没有翻页动画的时候是不会执行PageTurningAction

4.5. 设置默认配置

org.geometerplus.zlibrary.ui.android.library.ZLAndroidLibrary.java中保存着一些应用级别的设置。

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. 拦截跳转到下一章

对于FBReader_AS的源码我稍微修改了下,把PageTurnAction翻页行为的注册挪了下位置,并在里面添加了翻页拦截,当每进行翻页时会执行FBReader.java文件最底部的onTurnBackIntercept方法

    public boolean onTurnBackIntercept(boolean isForward) {
        //判断是否是准备翻到下一页的并且当前页是该章节的最后一页
        if (isForward && myFBReaderApp.BookTextView.getEndCursor().getParagraphCursor().isEndOfSection()) {
            //TODO   准备翻到下一章的拦截如果需要拦截返回true
            return false;
        }
        return false;
    }

这个函数传入一个参数isForward,如果为true,就是往右翻页,false就为左翻页。
该函数的返回值的类似于View中的onTouchEvent,如果返回值为true,阅读器就不会进行翻页,大家可以在return前做自己的事情,例如弹出一个请求框之类的,如果返回值为false,阅读器就会正常的翻页,不停止翻页。

先写着这么多吧,其实说多了,如果要改fbreader,真的必须看懂源码,这个文章就当作给写引子吧,灵活一点,多用用ctrl+左键和全局搜索,几乎都能找到你想要找的东西。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,067评论 1 32
  • lin秀阅读 116评论 0 0
  • 思前别。记时节。美人颜色如花发。美人归。天一涯。娟娟姮娥,三五满还亏。翠眉蝉鬓生离诀。遥望青楼心欲绝。梦中寻。卧巫...
    当归白芷阅读 297评论 0 0
  • 今天无意间发现,我的简书贝居然不是一直就那么显示的“0”字了,而是“1.000”这么多字。难道我一下子跳出来一千贝...
    邢欣Magpie阅读 685评论 17 19