上文基于recyclerview修改,没有自定义layoutmanager,其实并不是一个好的阅读器,缩放功能有限,放大了容易内存溢出.
本文,从修改vudroid源码来完成一个阅读器.
原作者ebookdroid更好,基于opengl实现的,修改更麻烦,代码非常抽象.已经是一个完整的项目,各项功能都完善了,也没有修改的必要.
策略模式的应用,让外部的uri在内部不同的策略中选一个显示的activity.
而在解码时又提供了策略模式,针对不同的类型,提供不同的解码实现.
它对页面进行了抽象rect(0,0,1.0,1.0);然后对它进行分割.分割的算法按层级.如果当前的缩放度加大了,达到层级需要再放大一级时,就会再分裂.
比如一个页面,分裂成4个,4个页面中的第一块区域,随时缩放级别加大,不满足了,就会继续分裂为4个,层级加一级.
默认的 PageTreeNode里面
private static final int SLICE_SIZE = 256 * 256 * 1;,这里1是旧设备,屏蔽较小,当前的设备都已经1080的了,可以设置为4.
对于page与pagetreenode现在没有什么要修改了.
- private void invalidateChildren() {
- boolean isThresholdHit = thresholdHit();
- boolean isVisible = isVisible();
- if (isThresholdHit && children == null && isVisible) {
- final int newThreshold = treeNodeDepthLevel * 2;
- children = new PageTreeNode[]
- {
- new PageTreeNode(documentView, new RectF(0, 0, 0.5f, 0.5f), page, newThreshold, this),
- new PageTreeNode(documentView, new RectF(0.5f, 0, 1.0f, 0.5f), page, newThreshold, this),
- new PageTreeNode(documentView, new RectF(0, 0.5f, 0.5f, 1.0f), page, newThreshold, this),
- new PageTreeNode(documentView, new RectF(0.5f, 0.5f, 1.0f, 1.0f), page, newThreshold, this)
- };
- }
- if (!isThresholdHit && getBitmap() != null || !isVisible) {
- recycleChildren();
- }
- }
当缩放级别大了,需要再分钱时,它会生成4个新的子节点,那么当前的节点就不再有bitmap了.
这是页面的逻辑分割,到具体的物理分割需要一个映射.
getTargetRect(),这个方法是对page.bounds与逻辑大小进行映射,得到一个目标rect.这样达到分块渲染的效果.
一个页面包含一个页面树
Page(DocumentView documentView, int index) {
this.documentView = documentView;
this.index = index;
node = new PageTreeNode(documentView, new RectF(0, 0, 1, 1), this, ZOOM_THRESHOLD, null);
}
页与页码都存起来了,子节点在node中,page本身没有画bitmap,它会调用node实现具体的绘图.
它处理长宽比例,页面宽度,背景绘制
invalidate方法,就是与view的同名,刷新视图用的.在外部的view调用.
这是view,处理滚动,绘制,页面加载,缩放等一切与view相关的.
因为它只是普通的view,那么layout后,高宽是固定的,如何实现多页面的滚动,就是scroll实现的,这样就会有偏移量.在滚动绘制时,需要处理每一个page的绘制.但如果每一个page都要绘制,性能是极差的.所以需要判断page是否在可见区.
先看它的流程:
- private void init() {
- if (isInitialized) {
- return;
- }
- final int width = decodeService.getEffectivePagesWidth();
- final int height = decodeService.getEffectivePagesHeight();
- for (int i = 0; i < decodeService.getPageCount(); i++) {
- pages.put(i, new Page(this, i));
- pages.get(i).setAspectRatio(width, height);
- }
- System.out.println("DecodeService:" + pages.size() + " pageToGoTo:" + pageToGoTo);
- isInitialized = true;
- currentPageModel.setPageCount(decodeService.getPageCount());
- invalidatePageSizes();
- goToPageImpl(pageToGoTo);
- }
先把所有页面的物理高宽计算出,存起来,然后初始化页面的view显示时的大小.这一步操作比较耗时,可以考虑进入非ui线程,而且页面的高宽是固定的,除非pdf被修改,这是可以缓存在文件中的.
invalidatePageSizes,初始化页面的实际显示的高宽,这个高宽是一个pdf根据dpi=72得到的高宽,显示在当前的view系统上多大,这时就会涉及到缩放了.
- 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;
- }
zoom默认是1,因为还没有进入缩放操作.但是这里的getpageheight却是根据view的宽高与pdf页面的宽高计算的.这个很好理解
float getPageHeight(int mainWidth, float zoom) {
return mainWidth / getAspectRatio() * zoom;
}
计算出所有页面高宽后,以后每一个页面,滚动到哪里就有数了.
接着它就开始跳转到页面.
上面是构造函数调用时的初始化.在布局后也会进行一次初始化
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
float scrollScaleRatio = getScrollScaleRatio();
invalidatePageSizes();
invalidateScroll(scrollScaleRatio);//只是处理滚动的位置
commitZoom();//这是调用page的invalidate.
}
这个初始化不一样的地方,它计算高宽后,要处理滚动,提交缩放.
滚动起来,如何更新呢?
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- // bound