本文要講的是,vudroid閱讀器選擇某個檔案後,如何展示該檔案的內容。
這裡不涉及vudroid是如何讀取檔案內容的,只是涉及讀取到檔案內容後,如何展示這些內容。
這裡涉及到三個類:DocumentView,Page,PageTreeNode。
DocumentView我們可以認為是一個顯示容器,它裡面存放的內容是一個個的頁(Page),而每個頁是由若干個PageTreeNode組成的。
因此,我們可以看到,DocumentView只是負責處理觸控螢幕時間、按鈕時間、滾動事件,當然這裡還涉及到放大縮小這個功能的處理;而Page是包括一頁的內容,包括頁邊界、常值內容等;PageTreeNode中存放的才是真正的常值內容。
1、DocumentView
這裡要做的內容包括按鍵處理、觸控螢幕事件處理(支援多點觸摸)、放大縮小功能處理、scroll事件處理:
1)按鍵事件處理,這裡你按上下左右鍵,頁面內容是可以上下左右移動的。
@Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_RIGHT: lineByLineMoveTo(1); return true; case KeyEvent.KEYCODE_DPAD_LEFT: lineByLineMoveTo(-1); return true; case KeyEvent.KEYCODE_DPAD_DOWN: verticalDpadScroll(1); return true; case KeyEvent.KEYCODE_DPAD_UP: verticalDpadScroll(-1); return true; } } return super.dispatchKeyEvent(event); }
2)觸控螢幕事件處理(這裡由於引入了多點觸摸的庫,因此是支援多點觸摸的)
第一段代碼時倒入多點觸摸的庫
private void initMultiTouchZoomIfAvailable(ZoomModel zoomModel) { try { multiTouchZoom = (MultiTouchZoom) Class.forName("org.vudroid.core.multitouch.MultiTouchZoomImpl").getConstructor(ZoomModel.class).newInstance(zoomModel); } catch (Exception e) { System.out.println("Multi touch zoom is not available: " + e); } }
第二段代碼是處理觸控螢幕事件
@Override public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); if (multiTouchZoom != null) { if (multiTouchZoom.onTouchEvent(ev)) { return true; } if (multiTouchZoom.isResetLastPointAfterZoom()) { setLastPosition(ev); multiTouchZoom.setResetLastPointAfterZoom(false); } } if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } velocityTracker.addMovement(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: stopScroller(); setLastPosition(ev); if (ev.getEventTime() - lastDownEventTime < DOUBLE_TAP_TIME) { zoomModel.toggleZoomControls(); } else { lastDownEventTime = ev.getEventTime(); } break; case MotionEvent.ACTION_MOVE: scrollBy((int) (lastX - ev.getX()), (int) (lastY - ev.getY())); setLastPosition(ev); break; case MotionEvent.ACTION_UP: velocityTracker.computeCurrentVelocity(1000); scroller.fling(getScrollX(), getScrollY(), (int) -velocityTracker.getXVelocity(), (int) -velocityTracker.getYVelocity(), getLeftLimit(), getRightLimit(), getTopLimit(), getBottomLimit()); velocityTracker.recycle(); velocityTracker = null; break; } return true; }
這段代碼中,首先是讓多點觸摸的處理類處理事件,如果該事件在這裡不被處理,則按普通觸控螢幕事件(區別於多點觸摸)處理。
3)、縮放功能處理
public void zoomChanged(float newZoom, float oldZoom) { inZoom = true; stopScroller(); final float ratio = newZoom / oldZoom; invalidatePageSizes(); scrollTo((int) ((getScrollX() + getWidth() / 2) * ratio - getWidth() / 2), (int) ((getScrollY() + getHeight() / 2) * ratio - getHeight() / 2)); postInvalidate(); }
這個應該是由ZoomListener觸發的事件,具體可以研究下ZoomListener的代碼。
4)scroll事件處理
只要調用了scrollTo和scrollBy都會觸發onScrollChanged函數。該函數的代碼是所有DocumentView事件的基礎,因為其他事件最終都會調用這一個代碼。
@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); // bounds could be not updated post(new Runnable() { public void run() { currentPageModel.setCurrentPageIndex(getCurrentPage()); } }); if (inZoom) { return; } // on scrollChanged can be called from scrollTo just after new layout applied so we should wait for relayout post(new Runnable() { public void run() { updatePageVisibility(); } }); }
這裡我們看到,DocumentView中要改變顯示內容的話,最終都是通過更改每個Page的顯示來實現的。讀這一段代碼的時候要注意以下這段函數:
void invalidatePageSizes() { if (!isInitialized) { return; } float heightAccum = 0; int width = getWidth(); float zoom = zoomModel.getZoom(); for (int i = 0; i < pages.size(); i++) { Page page = pages.get(i); float pageHeight = page.getPageHeight(width, zoom); page.setBounds(new RectF(0, heightAccum, width * zoom, heightAccum + pageHeight)); heightAccum += pageHeight; } }
這一端代碼定義每一頁的大小。根據PageTreeNode中的代碼判斷,這一部分顯示是這樣的,pages中的內容,我們可以看作是一頁頁首尾連在一起的捲軸,DocumentView相當於一個放大鏡,這個放大鏡放到什麼地方,就顯示什麼內容。
2、page
說實在的,我沒有搞清楚有了page以後,讓page來顯示內容就可以了,為什麼要加一個PageTreeNode類?
page類的功能實際上很直白:顯示一頁內容的上下分界線,滑動到某頁時在其中間顯示當前頁數(這個黑體頁數值會被PageTreeNode的內容覆蓋,可以認為是檔案內容沒有顯示之前,先顯示一個頁數值)。
3、PageTreeNode
PageTreeNode類是用來顯示檔案內容的,就是你在螢幕上看得到的實實在在的閱讀內容。
從以下函數知道PageTreeNode中的內容在PageTreeNode的方框與documentView的方框有交集時可以顯示。
private boolean isVisible() { return RectF.intersects(documentView.getViewRect(), getTargetRectF()); }
但是我沒弄明白pageSliceBounds、children、thresholdHit()等等這些成員是拿來做什麼的。從DecodeServiceBase的代碼中可以看出pageSliceBounds與讀取具體某種格式的檔案(如pdf檔案)有關係,但其具體在什麼地方有用,也只有等閱讀以下讀取pdf的那些代碼才能搞明白了。