FBReader是一个电子书开源项目,关于它之前有人发过5篇pdf叙述项目框架,这里主要讲讲FBReaderJ的翻页方式。
翻页方式的配置在ScrollingPreferences中
public final ZLEnumOption<ZLView.Animation> AnimationOption = new ZLEnumOption<ZLView.Animation>("Scrolling", "Animation", ZLView.Animation.slide);
总共有四种翻页方式
public static enum Animation { none, curl, slide, shift }
其中curl是仿真翻页方式,slide是滑动翻页。
负责绘制的是ZLAndroidWidget
@Override protected void onDraw(final Canvas canvas) { final Context context = getContext(); if (context instanceof ZLAndroidActivity) { ((ZLAndroidActivity)context).createWakeLock(); } else { System.err.println("A surprise: view's context is not a ZLAndroidActivity"); } super.onDraw(canvas); // final int w = getWidth(); // final int h = getMainAreaHeight(); if (getAnimationProvider().inProgress()) { onDrawInScrolling(canvas); } else { onDrawStatic(canvas); ZLApplication.Instance().onRepaintFinished(); } }
onDrawInScrolling是在滑动时绘制,onDrawStatic另外一个分支是画静态页面。
private void onDrawInScrolling(Canvas canvas) { final ZLView view = ZLApplication.Instance().getCurrentView(); // final int w = getWidth(); // final int h = getMainAreaHeight(); final AnimationProvider animator = getAnimationProvider(); final AnimationProvider.Mode oldMode = animator.getMode(); animator.doStep(); if (animator.inProgress()) { animator.draw(canvas); if (animator.getMode().Auto) { postInvalidate(); } drawFooter(canvas); } else { ....... } }
所以最后滑动效果委托给AnimationProvider完成,这里有两点要注意:
1、AnimationProvider的draw方法是一个模板方法,因为其中的drawInternal是一个抽象方法,这样子类就可以定制具体的翻页算法。
2、为什么不由ZLAndroidWidget的子类负责定制不同的翻页算法?
因为ZLAndroidWidget继承View,是直接在界面上显示的。如果在阅读器使用时改变翻页方式,就得更换ZLAndroidWidget的不同子类,所以就得调用removeView和addView,这样容易有bug,而且效率也不高。
具体的绘制算法再委托给AnimationProvider就不需要更换Activity的view;而且不同类层次的职责也更加明确,ZLAndroidWidget保存状态和管理图片,AnimationProvider只负责绘制滑动效果。
final void draw(Canvas canvas) { myBitmapManager.setSize(myWidth, myHeight); final long start = System.currentTimeMillis(); drawInternal(canvas); myDrawInfos.add(new DrawInfo(myEndX, myEndY, start, System.currentTimeMillis())); if (myDrawInfos.size() > 3) { myDrawInfos.remove(0); } } protected abstract void drawInternal(Canvas canvas);
负责滑动绘制的是SlideAnimationProvider,AnimationProvider总共有四个子类负责四种具体的翻页方式,这样就可以在运行时灵活改变翻页方式,而针对AnimationProvider写的代码还是不需要改变。
class SlideAnimationProvider extends SimpleAnimationProvider { private final Paint myPaint = new Paint(); SlideAnimationProvider(BitmapManager bitmapManager) { super(bitmapManager); } @Override protected void drawInternal(Canvas canvas) { canvas.drawBitmap(getBitmapTo(), 0, 0, myPaint); myPaint.setColor(Color.rgb(127, 127, 127)); if (myDirection.IsHorizontal) { final int dX = myEndX - myStartX; canvas.drawBitmap(getBitmapFrom(), dX, 0, myPaint); if (dX > 0 && dX < myWidth) { canvas.drawLine(dX, 0, dX, myHeight + 1, myPaint); } else if (dX < 0 && dX > -myWidth) { canvas.drawLine(dX + myWidth, 0, dX + myWidth, myHeight + 1, myPaint); } } else { final int dY = myEndY - myStartY; canvas.drawBitmap(getBitmapFrom(), 0, dY, myPaint); if (dY > 0 && dY < myHeight) { canvas.drawLine(0, dY, myWidth + 1, dY, myPaint); } else if (dY < 0 && dY > -myHeight) { canvas.drawLine(0, dY + myHeight, myWidth + 1, dY + myHeight, myPaint); } } } }
drawInternal集中体现了滑动绘制的逻辑。FBReaderJ支持垂直滑动和水平滑动,也是在ScrollingPreferences中配置。
对于水平滑动,先把要翻到的那页画出来:canvas.drawBitmap(getBitmapTo(), 0, 0, myPaint);
然后根据滑动的距离绘制以前的那页:canvas.drawBitmap(getBitmapFrom(), dX, 0, myPaint);
注意滑动的距离是开始水平位置减现在的水平位置:final int dX = myEndX - myStartX;而这两个位置初始时是赋值成一样的:(AnimationProvider)
final void startManualScrolling(int x, int y) { if (!myMode.Auto) { myMode = Mode.ManualScrolling; myEndX = myStartX = x; myEndY = myStartY = y; } }
而触发开始滑动的堆栈是:
public boolean onTouchEvent(MotionEvent event) ZLAndroidWidget.class
-->
public boolean onFingerMove(int x, int y) FBView.class
-->
private void startManualScrolling(int x, int y) FBView.class
-->
public void startManualScrolling(int x, int y, ZLView.Direction direction) ZLAndroidWidget.class
-->
final void startManualScrolling(int x, int y) AnimationProvider.class
startManualScrolling主要是记录了开始翻页时的那个触点。