FBReader阅读器各源码作用和知识点分析

ZLLibrary包下的core子包提供了要使用的各个抽象类,如ZLApplication

ZLAndroidApplication 本应用的Application

其实例化了三个类,分别做数据库,图片,library的操作,这些类并未使用,但我们可以使用它们。

数据库,首先创建或读取数据库,如果是新建则根据版本进行版本兼容,初始化增删改查的语句以供使用。(此处是通过构造方法的方式进行初始化的)

 

 

Bug的处理

自己捕获异常,并处理,当发生无法捕捉的异常(如运行时异常时)会转到这个Handler执行。

Thread.setDefaultUncaughtExceptionHandler(

new UncaughtExceptionHandler(this)

这里的UncaughtExceptionHandler就是异常处理类(继承自同名类)

在这个里面,我们可以开启一个activity用于处理和显示页面

Activity配置如下:

 <activity

            android:name="org.geometerplus.android.fbreader.crash.FixBooksDirectoryActivity"

            android:configChanges="orientation|keyboardHidden"

            android:process=":crash"

            android:theme="@android:style/Theme.Dialog" >

            <intent-filter>

                <action android:name="android.fbreader.action.CRASH" />

 

                <category android:name="android.intent.category.DEFAULT" />

 

                <data android:scheme="CachedCharStorageException" />

            intent-filter>

  activity>

使用的是action+UriIntent方式,如果能够找到解决方案则进入Activity

如果不行,则进入BugReportActivity

 

    <activity

            android:name=".library.BugReportActivity"

            android:configChanges="orientation|keyboardHidden"

            android:label="FBReader crash"

            android:process=":crash" />

 

 

 

FBReader基础------------ZLAndroidActivity

该类是FBReader的父类,实现功能如下:

·转屏判断

·亮度判断

·电量判断

·wakeLock

从使用的View可以知道,该Activity指定的layoutR.layout.main,其主要操作的就是org.geometerplus.zlibrary.ui.android.view.ZLAndroidWidget

 

org.geo.meterplus.zlibrary.ui.android.library包下,

 

 

进阶--FBReader 

onCreate中设置占据屏幕大小,添加功能(action),添加3popupFBreaderApp

onStart中检查屏幕大小是否符合,不符重开activity

Oncreate中添加的3popup设置位置

 

 

ZLTreeResource树形资源

实现类似于树形XML的效果,能够加载资源

errorMessage下的error

ZLResource.resource("errorMessage").getResource(“error”).getValue()

 

UIUtil UI提示工具类(提供Toast等提示)

 

阅读物的翻页(ZLAndroidWiget

一般来说我们对于这类小说阅读器应当有多种翻页以供使用,在本应用提供了四中翻页方式:nonecurlslideshift

翻页方式的改变十分简单,只需要更改ScrollingPreferences.AnimationOption即可

其调用在ZLAndroidWiget中的getAnimationProvider方法中。

所以,如果我们想自定义一个翻页动画,可以通过如下几步完成:

自定义一个类继承自AnimationProvider

实现其方法(必要实现方法:

ZLViewAnimation枚举中添加自身标识

public static enum Animation {

nonecurlslideshift,my

}

 

ScrollPreferences.AnimationOption中或在调用ZLAndroidWiget.getAnimationProvider()之前把该值改为Animation.my

ZLAndroidWigetgetAnimationProvider()方法中switch方法判断后添加

case my:

myAnimationProvider = new MyAnimationProvider(myBitmapManager);

break;

通过如上几步,就可以完成AnimationProvider的获取,接下来的使用工作由onDrawonSizeChange等方法完成,无需我们参与了!

 

在该类中无时无刻都在画图,但画图分成两种,一种是静止状态画图,即没有在翻页中,正在浏览页面内容时的画图,另一种是流动状态画图,正在翻页途中的画图

通过AnimationProvider.inProgress()的返回Boolean值就可以知道当前是否处于静止或流动状态。

 

 

小说文本的显示

文本是通过画View位图的方式进行显示的,位图在通过BitmapManager类进行动态生成的,我们需要的就是指定要显示的文本以及页数,还有位图宽高。

    /**

 * 静止中画

 * @param canvas

 */

private void onDrawStatic(Canvas canvas) {

//重新设置小说部分的宽高(主题部分的高是要减去底部

myBitmapManager.setSize(getWidth(), getMainAreaHeight());

//画出小说部分

canvas.drawBitmap(myBitmapManager.getBitmap(ZLView.PageIndex.current), 0, 0, myPaint);

drawFooter(canvas);

}

 

如何翻页?

onTouchEvent方法中获取点击位置的x,y值,如果是x值是从大到小,则说明是往前一页翻,如果x值是从小到大,则说明是往后一页翻。这样我们只需要传入不同的Mode即可。

所以我们需要在滚动动画结束后作出相应操作(如转到前页或后页)

要转页面可以调用ZLViewonScrollingFinished方法,该方法需要传入PageIndexPageIndex有三个值currentpervious, next ,分别代表当前页面,前方页面,后方页面。

如下:ZLView.PageIndex.current

view.onScrollingFinished(ZLView.PageIndex.current);

  

以上完成了翻页的工序,但是何时翻页呢?

需要在View.onTouchEvent()中进行判断。

在该方法中需要实现功能如下:

 /**

 * 滑动动作(重要)

 * 1.点击控件下部中间 出现菜单

 * 2.点击控件上部分中间出现导航(跳页的进度条)

 * 3.点击左部分跳转前页,右部分跳转后页

 * 4.滑动从左向右前页,否则后页

 */

具体计算方法,见代码ZLAndroidWiget.java

 

 

文字的承载

代码详见ZLTextView

在小说等作品中具有的文字是一页显示不完的,所以我们需要分页显示。每页的文字都显示在一个bitmap上,该bitmap的集合只需要保存2-3个,分别代表当前,前页,后页。

文字主要显示在ZLTextView上。所以当换页时我们需要在其上更换显示的文字,这三页分别都用ZLTextPage代表。

ZLAndroidWiget我们知道,当页面更换时(哪怕没有换页成功【即滑动距离不足以换页】)都会调用ZLView的抽象方法onScrollingFinished方法。该方法判断当前的操作【是否需要换页,如果传入current则不换页,如果是next则换到下页】。

以下为其中传入pervious的部分代码。即要换到前页!

 

final ZLTextPage swap = myNextPage;

myNextPage = myCurrentPage;

myCurrentPage = myPreviousPage;

myPreviousPage = swap;

myPreviousPage.reset();

if (myCurrentPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {

preparePaintInfo(myNextPage);

myCurrentPage.EndCursor.setCursor(myNextPage.StartCursor);

myCurrentPage.PaintState = PaintStateEnum.END_IS_KNOWN;

else if (!myCurrentPage.EndCursor.isNull() &&

   !myNextPage.StartCursor.isNull() &&

   !myCurrentPage.EndCursor.samePositionAs(myNextPage.StartCursor)) {

myNextPage.reset();

myNextPage.StartCursor.setCursor(myCurrentPage.EndCursor);

myNextPage.PaintState = PaintStateEnum.START_IS_KNOWN;

Application.getViewWidget().reset();

}

 

可以发现首先把 当前换到后页,前页换到当前,为前页赋值后页,最后前页重置,并加载新的文本。

即ABC换成 XAB  B为当前显示页,X为新加载页】

 

重新加载方式也很简单,调用preparePaintInfo

小说的分页

本部分是上面翻页的后续,从上面我们知道该小说是分成多个部分,每部分是做成bitmap进行显示的,每页都有自己的状态,正在显示的这页就是current,其前页就是pervious,后页就是next。而在其后页的后页,或前页的前页呢?通过观察ZLView内部类PageIndex发现,后页的后页为null,前页的前页也是null。即除了在显示页周围了2页其余皆为null

 

 

小说长点击和短点击的区分:

View中有performLongClick()

 

点击下去,首先判断是否有短点击(之前),如果有,则去除,否则发送延迟消息判断是否为长点击。记录按下位置,设置触摸标识为true

 

此时如果抬起,则说明

myPendingDoubleTap判断是否为双击

 

 

菜单的工作原理:

首先在ZLAndroidActivity及其子类的OnCreate中把需要的action通过fbReaderApp.addAction()的方式添加到FBReaderAppmap集合中。然后把想显示在该Activity上的菜单(就是刚刚addaction【此处actionid与之前addAction的必须相同】),添加到菜单中,并设置图标和名称以及设置点击事件,点击也十分简单,直接调用FBReaderApp.doAction方法,该方法会自动查找map集合中是否存在该Action如果存在,则进行调用。

具体方式如下:

final FBReaderApp fbReader = (FBReaderApp)FBReaderApp.Instance();

fbReader.addAction(ActionCode.SHOW_LIBRARY,new  

                              ShowLibraryAction(this, fbReader));

 

@Override

public boolean onCreateOptionsMenu(Menu menu) {

super.onCreateOptionsMenu(menu);

addMenuItem(menu, ActionCode.SHOW_LIBRARY, R.drawable.ic_menu_library);

             return true;

}

FBReaderaddMenuItem

private void addMenuItem(Menu menu, String actionId, int iconId) {

final ZLAndroidApplication application = (ZLAndroidApplication)getApplication();

application.myMainWindow.addMenuItem(menu, actionId, iconId, null);

}

ZLAndroidWindowaddMenuItem:

public void addMenuItem(Menu menu, String actionId, Integer iconId, String name) {

if (name == null) {

//通过动作不同,获取该动作名称

name = ZLResource.resource("menu").getResource(actionId).getValue();

}

final MenuItem menuItem = menu.add(name);

if (iconId != null) {

//为该菜单元素设置图标

menuItem.setIcon(iconId);

}

//设置点击事件

menuItem.setOnMenuItemClickListener(myMenuListener);

myMenuItemMap.put(menuItem, actionId);

}

菜单监听:

private final MenuItem.OnMenuItemClickListener myMenuListener =

new MenuItem.OnMenuItemClickListener() {

public boolean onMenuItemClick(MenuItem item) {

//通过不同的动作名称做出不同的响应

      //注意此处的getApplication()Activity下的同名方法是不同的

//第二个获得的是应用的Application,第一个则是ZLApplication(自定义)的继承类

getApplication().doAction(myMenuItemMap.get(item));

return true;

}

};

FBReaderAppdoAction

public final void doAction(String actionId) {

final ZLAction action = myIdToActionMap.get(actionId);

if (action != null) {

action.checkAndRun();

}

}

 

所以如果我们想自定义菜单,只需如下几部:

1自定义类,继承自FBAndroidAction,并实现run()方法

public class MyFirstAction extends FBAndroidAction {

 

MyFirstAction(FBReader baseActivity, FBReaderApp fbreader) {

        super(baseActivity, fbreader);

    }

@Override

protected void run() {

System.out.println("我的菜单被点击");

Toast.makeText(BaseActivity"我的菜单被点击", 1).show();

}

}

 

2为该Action设置一个独一无二的actionId,并写进ActionCode

 

3ZLAndroidActivity继承类中(Activity),获取FBReaderApp实例,并调用其addAction方法把该自定义动作加入

//我的菜单

fbReader.addAction("myMenu"new MyFirstAction(this, fbReader));

 

在菜单生成方法(OnCreateOptionsMenu)中通过addMenuItem(menu, ActionCode.SHOW_BOOKMARKS, R.drawable.ic_menu_bookmarks);

方法,传入动作actionId和图标图片id(如果没有则置空)

//添加我自己的菜单

addMenuItem(menu, "myMenu","我的菜单");

 

操作完毕!!

 

 

 

新的数据库操作方式:

1.首先通过Context.openOrCreateDatabase方式加载数据库文件 ,获取SqliteDatabase对象。

2.通过该对象的compileStatement方法,创建和编译SqliteStatement对象,该对象需要传递的参数可以用?代替

3.使用SqliteStatement,首先为?赋值,通过bindStringbindInt等方法可以完成。通过simpleQueryForString方法可以进行简单查询,并获取返回数据(复杂的不行),通过execute可以执行增加,修改,删除等不需要结果的操作。

 

屏幕朝向改变:

void rotate() {

View view = findViewById(R.id.main_view);

if (view != null) {

switch (getRequestedOrientation()) {

case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:

myOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;

break;

case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:

myOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;

break;

default:

if (view.getWidth() > view.getHeight()) {

myOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;

else {

myOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;

}

}

setRequestedOrientation(myOrientation);

}

}

 

根据当前宽高对比确定屏幕朝向,并改变。

 

更改屏幕亮度:

运行如下代码即可:

此处的precent1-100的值,当想让屏幕自定亮度,则attrs.screenBrightness可以传入-1f即可。

final WindowManager.LayoutParams attrs = getWindow().getAttributes();

attrs.screenBrightness = percent / 100.0f;

getWindow().setAttributes(attrs);

 

 

耗时操作执行队列:

在实际应用中某个操作需要分成多个耗时操作执行,而这时我们一般需要一个进度对话框来显示进度。如正在登陆,正在加载列表····等等

所以可以建立一个Queue用于保存需要操作的信息

·每个耗时操作可以放到一个Runnable中执行,每个操作都有其自己的消息(如正在登陆)

所以可以建立一个Pair,用于保存这两份信息。

·执行一个Runnable完成后,才继续执行之后的任务,所以需要进行锁定

详情见QueueMessageUtil

 

 

 

 

 

你可能感兴趣的:(FBReaderJ)