原本是想要探索FBReader是如何打開一本書的,可是發現涉及到的方方面面特別的多,索性咱們就來細細拆解,根據使用FBReader的步驟,按部就班的去品位FBReader這個龐大的工程究竟是怎麼運做的。android
想要對FBReader進行進一步的分析,首先要學會如何去使用這款軟件,知道它都有哪些功能提供給用戶。通過第一篇簡單的導入和相關設置,相信大夥已經可以順利運行app,那咱們就愉快的run起來吧。app
App運行起來以後,是這個樣子的,樸實的外表泥土的芬芳。 ide
固然了,這個app在操做的時候,是要點擊一塊固定的區域,才能彈出來一個操做菜單,進而去執行其餘的操做,爲了標識出這塊區域,就給它按照view的座標系方向,來作一下標記: 在清單文件,能夠發現FBReader的主Activity即爲FBReader,可謂是直截了當的命名。那咱們就進入FBReader一探究竟。 嗯.... 1053行.... 再看看裏面,奇奇怪怪各類變量、不認識的類、不知道幹啥的方法,看的着實讓人頭皮發麻,那索性去看看佈局文件,這總算能夠吧?很少說,看內容:<?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是怎麼去響應用戶單擊事件的了。並且也發現了諸如切換日夜間模式、設置閱讀頁面朝向、打開書籍目錄、書籍書籤等等一系列操做的定義,也就能夠開始進行一些簡單的設置處理了。
固然,因爲本人接觸此項目時間有限,並且書寫技術文章的經驗實在欠缺,過程當中不免會有存在錯誤或描述不清或語言累贅等等一些問題,還望你們可以諒解,同時也但願你們繼續給予指正。最後,感謝你們對個人支持,讓我有了強大的動力堅持下去。謝謝!下一章,咱們就去看一下,咱們能經過什麼辦法打開一本書,以及在一本書打開以前,都經歷了些什麼。