本文要讲的是,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的那些代码才能搞明白了。