原本是想要探索FBReader是如何打开一本书的,可是发现涉及到的方方面面特别的多,索性咱们就来细细拆解,根据使用FBReader的步骤,按部就班的去品位FBReader这个庞大的工程究竟是怎么运做的。android
想要对FBReader进行进一步的分析,首先要学会如何去使用这款软件,知道它都有哪些功能提供给用户。通过第一篇简单的导入和相关设置,相信大伙已经可以顺利运行app,那咱们就愉快的run起来吧。app
App运行起来以后,是这个样子的,朴实的外表泥土的芬芳。 ide
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<org.geometerplus.zlibrary.ui.android.view.ZLAndroidWidget
android:id="@+id/main_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:focusable="true"
android:scrollbars="vertical"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:fadeScrollbars="false"
/>
</RelativeLayout>
复制代码
很简单,也很清晰明了,就一个核心 ZLAndroidWidget,看起来这个核心的控件好像是显示和操做的最终也是惟一载体,这个时候再回看一下程序启动的页面,难免有两个疑问:布局
这两个疑问暂时先放在这里,咱们继续日后看。接下来,咱们就要去操做app打开一本书了,还记得咱们以前对首页划分的区域吗。咱们依次点击这9个区域,会发现只有当点击(1,2)这个区域的时候才可以弹出来操做菜单:post
刚才咱们看过布局文件,知道了FBReader这个Activity的布局中只有一个核心控件ZLAndroidWidget,并且从这个特殊行为(只有点 1,2 区域才弹出菜单)来看,应该是在触摸事件的处理过程当中,判断了用户点击的区域才作出相应的行为,究竟是不是这样呢?咱们直接进入ZLAndroidWidget,去一探究竟。动画
咱们直接来看它的onTouchEvent方法,鉴于关注的是点击事件,直接瞅准action up :ui
case MotionEvent.ACTION_UP:
if (myPendingDoubleTap) {
//double click
view.onFingerDoubleTap(x, y);
} else if (myLongClickPerformed) {
// long press
view.onFingerReleaseAfterLongPress(x, y);
} else {
if (myPendingLongClickRunnable != null) {
removeCallbacks(myPendingLongClickRunnable);
myPendingLongClickRunnable = null;
}
if (myPendingPress) {
if (view.isDoubleTapSupported()) {
if (myPendingShortClickRunnable == null) {
myPendingShortClickRunnable = new ShortClickRunnable();
}
postDelayed(myPendingShortClickRunnable, ViewConfiguration.getDoubleTapTimeout());
} else {
//single tap !
view.onFingerSingleTap(x, y);
}
} else {
view.onFingerRelease(x, y);
}
}
myPendingDoubleTap = false;
myPendingPress = false;
myScreenIsTouched = false;
break;
复制代码
能够看到其对各类触摸事件的判断,有双击、长按和单击,这里咱们去看单击事件的处理onFingerSingleTap(x,y),点进去后发现其定义再ZLView,惟一实如今FBView。点击(2,1)区域,断点跟进去以后能够发现,最终触发的方法是进入onFingerSingleTapLastResort(x,y):this
public void onFingerSingleTap(int x, int y) {
// 上面的代码省略...
onFingerSingleTapLastResort(x, y);
}
复制代码
进入onFingerSingleTapLastResort(x,y),这里须要注意一个点,判断了是否支持双击操做isDoubleTapSupported(),而且根据结果判断传递到后续的tap类型,这有什么用呢?暂且先无论,先看:spa
private void onFingerSingleTapLastResort(int x, int y) {
myReader.runAction(getZoneMap().getActionByCoordinates(
x, y, getContextWidth(), getContextHeight(),
isDoubleTapSupported() ? TapZoneMap.Tap.singleNotDoubleTap : TapZoneMap.Tap.singleTap
), x, y);
}
复制代码
这里出现了一个runAction,进入一瞧:插件
public final void runAction(String actionId, Object ... params) {
//从map中依据actionId去找到对应的action 那么map是何时存储这些actionId的呢?
final ZLAction action = myIdToActionMap.get(actionId);
if (action != null) {
// action找到了,执行action并把参数传过去
action.checkAndRun(params);
}
}
复制代码
再看checkAndRun,这个时候发现了一个新的基类ZLAction:
static abstract public class ZLAction {
public boolean isVisible() {
return true;
}
public boolean isEnabled() {
return isVisible();
}
public Boolean3 isChecked() {
return Boolean3.UNDEFINED;
}
public final boolean checkAndRun(Object ... params) {
if (isEnabled()) {//默认true
run(params);
return true;
}
return false;
}
abstract protected void run(Object ... params);
}
复制代码
如今咱们知道,onFingerSingleTapLastResort这个方法实际上是执行了actionId对应的action的run方法,而且传递过去的参数是x和y(触摸坐标),那么这个actionId是怎么来的呢?对应的action又干了什么呢?
根据以前onFingerSingleTapLastResort方法分步分析:
private void onFingerSingleTapLastResort(int x, int y) {
myReader.runAction(getZoneMap().getActionByCoordinates(...);
}
复制代码
1.getZoneMap获取TapZoneMap
private TapZoneMap getZoneMap() {
final PageTurningOptions prefs = myReader.PageTurningOptions;
String id = prefs.TapZoneMap.getValue();
if ("".equals(id)) {
id = prefs.Horizontal.getValue() ? "right_to_left" : "up";
}
if (myZoneMap == null || !id.equals(myZoneMap.Name)) {
myZoneMap = TapZoneMap.zoneMap(id);
}
return myZoneMap;
}
复制代码
2.翻页设置PageTurningOptions的TapZoneMap默认值为"":
public class PageTurningOptions {
public static enum FingerScrollingType {
byTap, //点击翻页
byFlick, //滑动翻页
byTapAndFlick // 点击和滑动翻页
}
//滑动方式 默承认点击翻页也可滑动翻页
public final ZLEnumOption<FingerScrollingType> FingerScrolling =
new ZLEnumOption<FingerScrollingType>("Scrolling", "Finger", FingerScrollingType.byTapAndFlick);
//默认动画方式
public final ZLEnumOption<ZLView.Animation> Animation =
new ZLEnumOption<ZLView.Animation>("Scrolling", "Animation", ZLView.Animation.slide);
//默认动画速度
public final ZLIntegerRangeOption AnimationSpeed =
new ZLIntegerRangeOption("Scrolling", "AnimationSpeed", 1, 10, 7);
//横向滑动 false为竖向滑动
public final ZLBooleanOption Horizontal =
new ZLBooleanOption("Scrolling", "Horizontal", true);
//点击区域规则约束
public final ZLStringOption TapZoneMap =
new ZLStringOption("Scrolling", "TapZoneMap", "");
}
复制代码
3.因为默认值为"",那么生成TapZoneMap时传入的id为"right_to_left"
4.TapZoneMap建立时根据传入id作了什么:
private TapZoneMap(String name) {
Name = name;
myOptionGroupName = "TapZones:" + name;
myHeight = new ZLIntegerRangeOption(myOptionGroupName, "Height", 2, 5, 3);// 默认值3 最小 2 最大 5
myWidth = new ZLIntegerRangeOption(myOptionGroupName, "Width", 2, 5, 3);// 默认值3 最小 2 最大5
// 最小分块为 2*2 最大为 5*5
// 加载名字为name的资源文件 !!
final ZLFile mapFile = ZLFile.createFileByPath(
"default/tapzones/" + name.toLowerCase() + ".xml"
);
XmlUtil.parseQuietly(mapFile, new Reader());//此处解析该资源文件
}
private class Reader extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
try {
if ("zone".equals(localName)) {
final Zone zone = new Zone(
Integer.parseInt(attributes.getValue("x")),
Integer.parseInt(attributes.getValue("y"))
);
final String action = attributes.getValue("action");//取出action
final String action2 = attributes.getValue("action2");//取出action2
if (action != null) {
myZoneMap.put(zone, createOptionForZone(zone, true, action));
}
if (action2 != null) {
myZoneMap2.put(zone, createOptionForZone(zone, false, action2));
}
} else if ("tapZones".equals(localName)) {
final String v = attributes.getValue("v");
// 获取xml中定义的横向分块数
if (v != null) {
myHeight.setValue(Integer.parseInt(v));
}
final String h = attributes.getValue("h");
// 获取xml中定义的竖向分块数
if (h != null) {
myWidth.setValue(Integer.parseInt(h));
}
}
} catch (Throwable e) {
}
}
}
复制代码
5.资源文件位置,和其内容定义:
咱们知道默认加载的资源为right_to_left,那么就进去看一下:
这里的区域划分,再回看一下上面区域划分的图,找到咱们点击能弹出菜单的区域(1,2),能够看到定义了action2="menu",彷佛跟咱们想象的匹配起来了啊。并且能够发现有些区域定义了两个,action和action2,那么为何有的会有两个呢?这两个是何时用的呢?带着疑问咱们继续探索。
6.前面几步已经获取到了TapZoneMap,接着看其方法getActionByCoordinates:
public String getActionByCoordinates(int x, int y, int width, int height, Tap tap) {
//忽略一部分代码...
// 这里myWidth和myHeight的默认值为3(3*3),与划分的区域块数相同 并且在解析xml的时候还会设置一下,使其与xml中定义的数值一致
// 所以至关于 x / (width / 3) 横向第几块 y / (height / 3) 竖向第几块
return getActionByZone(myWidth.getValue() * x / width, myHeight.getValue() * y / height, tap);
}
复制代码
继续跟进到getActionByZone:
public String getActionByZone(int h, int v, Tap tap) {
final ZLStringOption option = getOptionByZone(new Zone(h, v), tap);
return option != null ? option.getValue() : null;
}
复制代码
最后进入getOptionByZone:
private ZLStringOption getOptionByZone(Zone zone, Tap tap) {
switch (tap) {
default:
return null;
case singleTap:
{
final ZLStringOption option = myZoneMap.get(zone);
return option != null ? option : myZoneMap2.get(zone);
}
case singleNotDoubleTap:
return myZoneMap.get(zone);
case doubleTap:
return myZoneMap2.get(zone);
}
}
复制代码
还记得以前有个方法对是否支持双击的判断么。支持双击tap则为singleNotDoubleTap,不然为singleTap,并且为singleTap时若是action为空,那么就取action2的值。至此,咱们总算是获得了对应的actionId = "menu"。
经过上面的追踪,咱们已经获得了最终的指令:actionId。针对于actionId,又是怎么识别和采起实际行动的呢?咱们接着往下看。
此次咱们进入主Activity FBReader,从生命周期起始的onCreate看起:
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//省略部分代码...
//本地书柜
myFBReaderApp.addAction(ActionCode.SHOW_LIBRARY, new ShowLibraryAction(this, myFBReaderApp));
//阅读相关设置
myFBReaderApp.addAction(ActionCode.SHOW_PREFERENCES, new ShowPreferencesAction(this, myFBReaderApp));
//书籍信息
myFBReaderApp.addAction(ActionCode.SHOW_BOOK_INFO, new ShowBookInfoAction(this, myFBReaderApp));
//本书目录
myFBReaderApp.addAction(ActionCode.SHOW_TOC, new ShowTOCAction(this, myFBReaderApp));
//个人书签
myFBReaderApp.addAction(ActionCode.SHOW_BOOKMARKS, new ShowBookmarksAction(this, myFBReaderApp));
//在线书库
myFBReaderApp.addAction(ActionCode.SHOW_NETWORK_LIBRARY, new ShowNetworkLibraryAction(this, myFBReaderApp));
//显示菜单
myFBReaderApp.addAction(ActionCode.SHOW_MENU, new ShowMenuAction(this, myFBReaderApp));
//显示当前阅读进度pop
myFBReaderApp.addAction(ActionCode.SHOW_NAVIGATION, new ShowNavigationAction(this, myFBReaderApp));
//内容查找
myFBReaderApp.addAction(ActionCode.SEARCH, new SearchAction(this, myFBReaderApp));
//共享书籍
myFBReaderApp.addAction(ActionCode.SHARE_BOOK, new ShareBookAction(this, myFBReaderApp));
//显示长按选中区域
myFBReaderApp.addAction(ActionCode.SELECTION_SHOW_PANEL, new SelectionShowPanelAction(this, myFBReaderApp));
//隐藏长按选中区域
myFBReaderApp.addAction(ActionCode.SELECTION_HIDE_PANEL, new SelectionHidePanelAction(this, myFBReaderApp));
//复制选中内容到剪切板
myFBReaderApp.addAction(ActionCode.SELECTION_COPY_TO_CLIPBOARD, new SelectionCopyAction(this, myFBReaderApp));
//分享选中内容
myFBReaderApp.addAction(ActionCode.SELECTION_SHARE, new SelectionShareAction(this, myFBReaderApp));
//字典查询选中内容
myFBReaderApp.addAction(ActionCode.SELECTION_TRANSLATE, new SelectionTranslateAction(this, myFBReaderApp));
//在选中位置添加书签
myFBReaderApp.addAction(ActionCode.SELECTION_BOOKMARK, new SelectionBookmarkAction(this, myFBReaderApp));
//点击处内容类型为ZLTextRegion.ExtensionFilter时触发此action
myFBReaderApp.addAction(ActionCode.DISPLAY_BOOK_POPUP, new DisplayBookPopupAction(this, myFBReaderApp));
//点击处可跳转指定位置如目录
myFBReaderApp.addAction(ActionCode.PROCESS_HYPERLINK, new ProcessHyperlinkAction(this, myFBReaderApp));
//点击处为视频
myFBReaderApp.addAction(ActionCode.OPEN_VIDEO, new OpenVideoAction(this, myFBReaderApp));
//隐藏toast
myFBReaderApp.addAction(ActionCode.HIDE_TOAST, new HideToastAction(this, myFBReaderApp));
//点击返回按钮时,弹出菜单
myFBReaderApp.addAction(ActionCode.SHOW_CANCEL_MENU, new ShowCancelMenuAction(this, myFBReaderApp));
//开始屏幕(会打开帮助文档)
myFBReaderApp.addAction(ActionCode.OPEN_START_SCREEN, new StartScreenAction(this, myFBReaderApp));
//设置屏幕朝向跟随系统当前
myFBReaderApp.addAction(ActionCode.SET_SCREEN_ORIENTATION_SYSTEM, new SetScreenOrientationAction(this, myFBReaderApp, ZLibrary.SCREEN_ORIENTATION_SYSTEM));
//设置屏幕朝向跟随陀螺仪
myFBReaderApp.addAction(ActionCode.SET_SCREEN_ORIENTATION_SENSOR, new SetScreenOrientationAction(this, myFBReaderApp, ZLibrary.SCREEN_ORIENTATION_SENSOR));
//设置屏幕竖直朝向
myFBReaderApp.addAction(ActionCode.SET_SCREEN_ORIENTATION_PORTRAIT, new SetScreenOrientationAction(this, myFBReaderApp, ZLibrary.SCREEN_ORIENTATION_PORTRAIT));
//设置屏幕水平朝向
myFBReaderApp.addAction(ActionCode.SET_SCREEN_ORIENTATION_LANDSCAPE, new SetScreenOrientationAction(this, myFBReaderApp, ZLibrary.SCREEN_ORIENTATION_LANDSCAPE));
if (getZLibrary().supportsAllOrientations()) {
//可反向竖直
myFBReaderApp.addAction(ActionCode.SET_SCREEN_ORIENTATION_REVERSE_PORTRAIT, new SetScreenOrientationAction(this, myFBReaderApp, ZLibrary.SCREEN_ORIENTATION_REVERSE_PORTRAIT));
//可反向水平
myFBReaderApp.addAction(ActionCode.SET_SCREEN_ORIENTATION_REVERSE_LANDSCAPE, new SetScreenOrientationAction(this, myFBReaderApp, ZLibrary.SCREEN_ORIENTATION_REVERSE_LANDSCAPE));
}
//帮助
myFBReaderApp.addAction(ActionCode.OPEN_WEB_HELP, new OpenWebHelpAction(this, myFBReaderApp));
//安装插件
myFBReaderApp.addAction(ActionCode.INSTALL_PLUGINS, new InstallPluginsAction(this, myFBReaderApp));
//切换日间模式
myFBReaderApp.addAction(ActionCode.SWITCH_TO_DAY_PROFILE, new SwitchProfileAction(this, myFBReaderApp, ColorProfile.DAY));
//切换夜间模式
myFBReaderApp.addAction(ActionCode.SWITCH_TO_NIGHT_PROFILE, new SwitchProfileAction(this, myFBReaderApp, ColorProfile.NIGHT));
//省略部分代码...
}
复制代码
再来看看myFBReaderApp的addAction方法:
public final void addAction(String actionId, ZLAction action) {
myIdToActionMap.put(actionId, action);
}
复制代码
很明显,在onCreate的时候,已经将这些可操做行为id和对应的action存储到了myFBReaderApp的myIdToActionMap,还记得以前单击事件以后调用的runAction吗:
public final void runAction(String actionId, Object ... params) {
final ZLAction action = myIdToActionMap.get(actionId);
if (action != null) {
action.checkAndRun(params);
}
}
复制代码
到此,咱们由用户“第一个有效”事件,单击弹出菜单,大体了解了FBReader是怎么去响应用户单击事件的了。并且也发现了诸如切换日夜间模式、设置阅读页面朝向、打开书籍目录、书籍书签等等一系列操做的定义,也就能够开始进行一些简单的设置处理了。
固然,因为本人接触此项目时间有限,并且书写技术文章的经验实在欠缺,过程当中不免会有存在错误或描述不清或语言累赘等等一些问题,还望你们可以谅解,同时也但愿你们继续给予指正。最后,感谢你们对个人支持,让我有了强大的动力坚持下去。谢谢!下一章,咱们就去看一下,咱们能经过什么办法打开一本书,以及在一本书打开以前,都经历了些什么。