FBReader是如何讀取緩存文件內(nèi)容,并生成每一頁(yè)Bitmap內(nèi)容的呢?
經(jīng)過(guò)上一篇的分析,我們已經(jīng)知道,FBRreader在繪制時(shí)是獲取每一頁(yè)對(duì)應(yīng)的bitmap,然后再進(jìn)行繪制的。同時(shí),在繪制完當(dāng)前頁(yè)之后,會(huì)通過(guò)Executors.newSingleThreadExecutor來(lái)準(zhǔn)備下一頁(yè)的bitmap。
上一篇提到了一個(gè)重要的角色——ZLZLTextPlainModel。里面記錄了native生成的緩存文件路徑以及緩存文件個(gè)數(shù)。并且,其實(shí)例是在native解析BookModel時(shí)通過(guò)調(diào)用java方法創(chuàng)建并且set到BookModel中的。
一、數(shù)據(jù)注入——“瀑布”傾瀉的開(kāi)始
再次回到FBReaderApp這個(gè)類的openBookInternal,繼續(xù)探索數(shù)據(jù)解析之后,內(nèi)容的“瀑布”是怎么被開(kāi)啟的:
private synchronized void openBookInternal(final Book book, Bookmark bookmark, boolean force) {//忽略部分代碼...try {//忽略部分代碼...//native解析BookModelModel = BookModel.createModel(book, plugin);//保存bookCollection.saveBook(book);ZLTextHyphenator.Instance().load(book.getLanguage());//數(shù)據(jù)注入BookTextView.setModel(Model.getTextModel());//忽略部分代碼...} catch (BookReadingException e) {processException(e);}getViewWidget().reset();getViewWidget().repaint();//忽略部分代碼...
}
復(fù)制代碼
這里有一個(gè)核心的方法,會(huì)將數(shù)據(jù)注入到view中:
BookTextView.setModel(Model.getTextModel());
復(fù)制代碼
這里的BookTextView為FBView的實(shí)例,追溯其setModel方法,最終在ZLTextView中:
public synchronized void setModel(ZLTextModel model) {myCursorManager = model != null ? new CursorManager(model, getExtensionManager()) : null;//忽略部分代碼...myModel = model;myCurrentPage.reset();myPreviousPage.reset();myNextPage.reset();if (myModel != null) {final int paragraphsNumber = myModel.getParagraphsNumber();if (paragraphsNumber > 0) {myCurrentPage.moveStartCursor(myCursorManager.get(0));}}Application.getViewWidget().reset();
}
復(fù)制代碼
這里有這么幾件事需要注意一下:
- 判空model,生成CursorManager
- 重置上一頁(yè)、當(dāng)前頁(yè)、下一頁(yè)
- 判空myModel,通過(guò)CursorManager獲取第一自然段的cursor
- 將當(dāng)前currentpage內(nèi)容起始位置指向第一自然段的cursor
- 重置Application.getViewWidget
分別看一下,這幾步都是做了些什么工作:
-
在model不為空的情況下會(huì)創(chuàng)建CusorManger,那么這個(gè)CusorManger是什么呢?
final class CursorManager extends LruCache<Integer,ZLTextParagraphCursor> {private final ZLTextModel myModel;final ExtensionElementManager ExtensionManager;CursorManager(ZLTextModel model, ExtensionElementManager extManager) {super(200); // max 200 cursors in the cachemyModel = model;ExtensionManager = extManager;}@Overrideprotected ZLTextParagraphCursor create(Integer index) {return new ZLTextParagraphCursor(this, myModel, index);}} 復(fù)制代碼
原來(lái)CusorManger是繼承自LruCache<Integer,ZLTextParagraphCursor>,而且其最大緩存200個(gè)cursor,并且重寫create方法,在調(diào)用get(integer)時(shí),如果獲取不到則會(huì)通過(guò)create創(chuàng)建integer對(duì)應(yīng)的ZLTextParagraphCurosr對(duì)象。
再來(lái)看一下ZLTextParagraphCurosr,該類是第index自然段的cursor:
public final class ZLTextParagraphCursor {//忽略部分代碼...ZLTextParagraphCursor(CursorManager cManager, ZLTextModel model, int index) {CursorManager = cManager;Model = model;//段落角標(biāo)Index = Math.min(index, model.getParagraphsNumber() - 1);fill();}//忽略部分代碼...} 復(fù)制代碼
2.重置上一頁(yè)、當(dāng)前頁(yè)、下一頁(yè)(ZLTextPage)
final class ZLTextPage {final ZLTextWordCursor StartCursor = new ZLTextWordCursor();final ZLTextWordCursor EndCursor = new ZLTextWordCursor();final ArrayList<ZLTextLineInfo> LineInfos = new ArrayList<ZLTextLineInfo>();int PaintState = PaintStateEnum.NOTHING_TO_PAINT;void reset() {StartCursor.reset();EndCursor.reset();LineInfos.clear();PaintState = PaintStateEnum.NOTHING_TO_PAINT;}
}
復(fù)制代碼
看起來(lái)好像每一頁(yè)的內(nèi)容范圍,是由起始的starCurosr和終止的endCursor定位的?來(lái)看看ZLTextWordCursor:
public final class ZLTextWordCursor extends ZLTextPosition {private ZLTextParagraphCursor myParagraphCursor;private int myElementIndex;private int myCharIndex;public void reset() {myParagraphCursor = null;myElementIndex = 0;myCharIndex = 0;}}
復(fù)制代碼
3.判空model,不為空時(shí)獲取cursormanager.get(0),我們知道在初始創(chuàng)建cursormanager時(shí),內(nèi)部是沒(méi)有緩存的內(nèi)容的,這時(shí)會(huì)通過(guò)create創(chuàng)建ZLTextParagraphCursor對(duì)象。
4.將當(dāng)前頁(yè)的起始curosr移動(dòng)至上一步獲取的curosr處,并將endcuror重置:
ZLTextPage.class
final ArrayList<ZLTextLineInfo> LineInfos = new ArrayList<ZLTextLineInfo>();
void moveStartCursor(ZLTextParagraphCursor cursor) {StartCursor.setCursor(cursor);EndCursor.reset();LineInfos.clear();PaintState = PaintStateEnum.START_IS_KNOWN;
}ZLTextWordCursor.class
public void setCursor(ZLTextParagraphCursor paragraphCursor) {myParagraphCursor = paragraphCursor;myElementIndex = 0;myCharIndex = 0;
}
復(fù)制代碼
5.重置Application.getViewWidget的重置,最終在bitmapmanager中:
void reset() {for (int i = 0; i < SIZE; ++i) {myIndexes[i] = null;//置空緩存的bitmap}
}
復(fù)制代碼
二、ZLTextParagraphCursor開(kāi)啟數(shù)據(jù)倉(cāng)庫(kù)的“大門”
在ZLTextParagraphCursor初始化時(shí),調(diào)用fill方法:
ZLTextParagraphCursor(CursorManager cManager, ZLTextModel model, int index) {CursorManager = cManager;Model = model;Index = Math.min(index, model.getParagraphsNumber() - 1);fill();
}void fill() {ZLTextParagraph paragraph = Model.getParagraph(Index);switch (paragraph.getKind()) {case ZLTextParagraph.Kind.TEXT_PARAGRAPH:new Processor(paragraph, CursorManager.ExtensionManager, new LineBreaker(Model.getLanguage()), Model.getMarks(), Index, myElements).fill();break;//忽略部分代碼...}
}
復(fù)制代碼
發(fā)現(xiàn)會(huì)通過(guò)model獲取index自然段對(duì)應(yīng)的paragraph,我們知道m(xù)odel為ZLTextPlainModel的實(shí)例:
public final ZLTextParagraph getParagraph(int index) {//獲取index自然段的kind,數(shù)組myParagraphKinds數(shù)據(jù)由native解析得到final byte kind = myParagraphKinds[index];return (kind == ZLTextParagraph.Kind.TEXT_PARAGRAPH) ?new ZLTextParagraphImpl(this, index) :new ZLTextSpecialParagraphImpl(kind, this, index);
}
復(fù)制代碼
一般的情況下,自然段均為TEXT_PARAGRAPH,相應(yīng)的就會(huì)生成ZLTextParagraphImpl:
class ZLTextParagraphImpl implements ZLTextParagraph {private final ZLTextPlainModel myModel;private final int myIndex;ZLTextParagraphImpl(ZLTextPlainModel model, int index) {myModel = model;myIndex = index;}public EntryIterator iterator() {return myModel.new EntryIteratorImpl(myIndex);}public byte getKind() {return Kind.TEXT_PARAGRAPH;}
}
復(fù)制代碼
這里有一個(gè)地方需要注意,那就是iterator()方法返回的迭代器對(duì)象EntryIteratorImpl:
tips: EntryIteratorImpl為ZLTextPlainModel的非靜態(tài)內(nèi)部類EntryIteratorImpl(int index) {reset(index);
}void reset(int index) {//計(jì)數(shù)器清0myCounter = 0;//獲取native讀取后,index段落內(nèi)容長(zhǎng)度myLength = myParagraphLengths[index];//獲取native讀取后,index段落內(nèi)容在哪個(gè)ncache文件中myDataIndex = myStartEntryIndices[index];//獲取native讀取后,index段落內(nèi)容起始位置在ncache內(nèi)容中的偏移myDataOffset = myStartEntryOffsets[index];
}
復(fù)制代碼
接下來(lái),由于段落類型為TEXT_PARAGRAPH,那么就會(huì)執(zhí)行new Processor(...).fill():
void fill() {//忽略部分代碼...final ArrayList<ZLTextElement> elements = myElements;for (ZLTextParagraph.EntryIterator it = myParagraph.iterator(); it.next(); ) {switch (it.getType()) {case ZLTextParagraph.Entry.TEXT:processTextEntry(it.getTextData(), it.getTextOffset(), it.getTextLength(), hyperlink);break;case ZLTextParagraph.Entry.CONTROL://忽略部分代碼...break;case ZLTextParagraph.Entry.HYPERLINK_CONTROL://忽略部分代碼...break;case ZLTextParagraph.Entry.IMAGE:final ZLImageEntry imageEntry = it.getImageEntry();final ZLImage image = imageEntry.getImage();if (image != null) {ZLImageData data = ZLImageManager.Instance().getImageData(image);if (data != null) {if (hyperlink != null) {hyperlink.addElementIndex(elements.size());}elements.add(new ZLTextImageElement(imageEntry.Id, data, image.getURI(), imageEntry.IsCover));}}break;case ZLTextParagraph.Entry.AUDIO:break;case ZLTextParagraph.Entry.VIDEO:break;case ZLTextParagraph.Entry.EXTENSION://忽略部分代碼...break;case ZLTextParagraph.Entry.STYLE_CSS:case ZLTextParagraph.Entry.STYLE_OTHER:elements.add(new ZLTextStyleElement(it.getStyleEntry()));break;case ZLTextParagraph.Entry.STYLE_CLOSE:elements.add(ZLTextElement.StyleClose);break;case ZLTextParagraph.Entry.FIXED_HSPACE:elements.add(ZLTextFixedHSpaceElement.getElement(it.getFixedHSpaceLength()));break;}}
}
復(fù)制代碼
這里會(huì)進(jìn)入一個(gè)for循環(huán),循環(huán)的條件是it.next(),而it是myParagraph.iterator(),這個(gè)上一步我們已經(jīng)分析過(guò),針對(duì)kind為TEXT_PARAGRAPH的自然段,iterator返回的對(duì)象為EntryIteratorImpl,那么就看一下EntryIteratorImpl的next方法:
public boolean next() {if (myCounter >= myLength) {return false;}int dataOffset = myDataOffset;//該段落起始游標(biāo)char[] data = myStorage.block(myDataIndex);if (data == null) {return false;}if (dataOffset >= data.length) {data = myStorage.block(++myDataIndex);if (data == null) {return false;}dataOffset = 0;}short first = (short)data[dataOffset];byte type = (byte)first;if (type == 0) {data = myStorage.block(++myDataIndex);if (data == null) {return false;}dataOffset = 0;first = (short)data[0];type = (byte)first;}myType = type;++dataOffset;switch (type) {case ZLTextParagraph.Entry.TEXT:{int textLength = (int)data[dataOffset++];textLength += (((int)data[dataOffset++]) << 16);textLength = Math.min(textLength, data.length - dataOffset);myTextLength = textLength;myTextData = data;myTextOffset = dataOffset;dataOffset += textLength;break;}case ZLTextParagraph.Entry.CONTROL:{//忽略部分代碼...break;}case ZLTextParagraph.Entry.HYPERLINK_CONTROL:{//忽略部分代碼...break;}case ZLTextParagraph.Entry.IMAGE:{final short vOffset = (short)data[dataOffset++];final short len = (short)data[dataOffset++];final String id = new String(data, dataOffset, len);dataOffset += len;final boolean isCover = data[dataOffset++] != 0;myImageEntry = new ZLImageEntry(myImageMap, id, vOffset, isCover);break;}case ZLTextParagraph.Entry.FIXED_HSPACE://忽略部分代碼...break;case ZLTextParagraph.Entry.STYLE_CSS:case ZLTextParagraph.Entry.STYLE_OTHER:{//忽略部分代碼...}case ZLTextParagraph.Entry.STYLE_CLOSE:// No databreak;case ZLTextParagraph.Entry.RESET_BIDI:// No databreak;case ZLTextParagraph.Entry.AUDIO:// No databreak;case ZLTextParagraph.Entry.VIDEO:{//忽略部分代碼...break;}case ZLTextParagraph.Entry.EXTENSION:{//忽略部分代碼...break;}}++myCounter;myDataOffset = dataOffset;return true;
}
復(fù)制代碼
在next方法中,出現(xiàn)了之前分析到的一個(gè)角色CachedCharStorage,首先會(huì)調(diào)用其block方法:
protected final ArrayList<WeakReference<char[]>> myArray =new ArrayList<WeakReference<char[]>>();public char[] block(int index) {if (index < 0 || index >= myArray.size()) {return null;}char[] block = myArray.get(index).get();if (block == null) {try {File file = new File(fileName(index));int size = (int)file.length();if (size < 0) {throw new CachedCharStorageException(exceptionMessage(index, "size = " + size));}block = new char[size / 2];InputStreamReader reader =new InputStreamReader(new FileInputStream(file),"UTF-16LE");final int rd = reader.read(block);if (rd != block.length) {throw new CachedCharStorageException(exceptionMessage(index, "; " + rd + " != " + block.length));}reader.close();} catch (IOException e) {throw new CachedCharStorageException(exceptionMessage(index, null), e);}myArray.set(index, new WeakReference<char[]>(block));}return block;
}
復(fù)制代碼
在調(diào)用block方法時(shí),傳入的參數(shù)為myDataIndex,該參數(shù)指明了當(dāng)前自然段的內(nèi)容在哪個(gè)ncahce文件中。不難分析出,next方法主要的作用:
- 讀取要獲取的自然段所在ncache,如果CachedCharStorage中已緩存則取緩存,否則直接讀取對(duì)應(yīng)的ncache文件
- 必要時(shí)讀取下一個(gè)ncache文件(當(dāng)前段落內(nèi)容起始在x.ncache中,但終止在x+1.ncahce中)
- 根據(jù)native讀取的段落內(nèi)容長(zhǎng)度,每次調(diào)用next讀取一個(gè)內(nèi)容元素,并將讀取到的元素類型(可能是TEXT、IMAGE等格式)、數(shù)據(jù)內(nèi)容、offset、長(zhǎng)度等記錄下來(lái)
這里,我們?cè)俅位氐絝or循環(huán)。通過(guò)next方法,我們已經(jīng)知道,該方法會(huì)讀取一個(gè)元素,并將讀取到的元素類型等信息保存下來(lái),查看for循環(huán)內(nèi)部代碼發(fā)現(xiàn),后續(xù)會(huì)根據(jù)讀取到的元素類型,進(jìn)行數(shù)據(jù)的原始組裝,并最終保存到ZLTextParagraphCursor的ArrayList集合中。即通過(guò)此fill方法最終將index自然段的每一個(gè)元素讀取出來(lái),并存入了集合中。
三、通過(guò)數(shù)據(jù)倉(cāng)庫(kù)“大門”,拉取所需內(nèi)容數(shù)據(jù),繪制頁(yè)面對(duì)應(yīng)bitmap
在初始化ZLTextParagraphCursor時(shí),我們已經(jīng)知道其通過(guò)fill方法,已經(jīng)將內(nèi)容解析出來(lái)。這時(shí),我們?cè)倩乜匆幌聅etModel方法:
public synchronized void setModel(ZLTextModel model) {//忽略部分代碼...if (myModel != null) {final int paragraphsNumber = myModel.getParagraphsNumber();if (paragraphsNumber > 0) {myCurrentPage.moveStartCursor(myCursorManager.get(0));}}//忽略部分代碼...
}
復(fù)制代碼
會(huì)將當(dāng)前頁(yè)面的startCursor移動(dòng)到第一自然段,并將當(dāng)前頁(yè)面的PaintState設(shè)置為START_IS_KNOWN。這個(gè)時(shí)候頁(yè)面已經(jīng)準(zhǔn)備就緒,等待“發(fā)令槍”響了!那么“發(fā)令槍”,是在什么時(shí)候打響的呢?這就又要回顧一下之前的一個(gè)老朋友,FBReader界面唯一的控件——ZLAndroidWidget。它的onDraw方法我們已經(jīng)分析過(guò),在靜止?fàn)顟B(tài)時(shí),會(huì)調(diào)用onDrawStatic:
ZLAndroidWidget.class
private void onDrawStatic(final Canvas canvas) {canvas.drawBitmap(myBitmapManager.getBitmap(ZLView.PageIndex.current), 0, 0, myPaint);//忽略部分代碼...
}BitmapManagerImpl.class
public Bitmap getBitmap(ZLView.PageIndex index) {//忽略部分代碼...myWidget.drawOnBitmap(myBitmaps[iIndex], index);return myBitmaps[iIndex];
}ZLAndroidWidget.class
void drawOnBitmap(Bitmap bitmap, ZLView.PageIndex index) {final ZLView view = ZLApplication.Instance().getCurrentView();if (view == null) {return;}final ZLAndroidPaintContext context = new ZLAndroidPaintContext(mySystemInfo,new Canvas(bitmap),new ZLAndroidPaintContext.Geometry(getWidth(),getHeight(),getWidth(),getMainAreaHeight(),0,0),view.isScrollbarShown() ? getVerticalScrollbarWidth() : 0);view.paint(context, index);
}
復(fù)制代碼
ZLApplication.Instance().getCurrentView()返回的對(duì)象即為setModel時(shí)的BookTextView,那么就會(huì)調(diào)用其paint方法:
public synchronized void paint(ZLPaintContext context, PageIndex pageIndex) {setContext(context);final ZLFile wallpaper = getWallpaperFile();if (wallpaper != null) {context.clear(wallpaper, getFillMode());} else {context.clear(getBackgroundColor());}if (myModel == null || myModel.getParagraphsNumber() == 0) {return;}ZLTextPage page;switch (pageIndex) {default:case current:page = myCurrentPage;break;case previous:page = myPreviousPage;if (myPreviousPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {preparePaintInfo(myCurrentPage);myPreviousPage.EndCursor.setCursor(myCurrentPage.StartCursor);myPreviousPage.PaintState = PaintStateEnum.END_IS_KNOWN;}break;case next:page = myNextPage;if (myNextPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {preparePaintInfo(myCurrentPage);myNextPage.StartCursor.setCursor(myCurrentPage.EndCursor);myNextPage.PaintState = PaintStateEnum.START_IS_KNOWN;}}page.TextElementMap.clear();preparePaintInfo(page);if (page.StartCursor.isNull() || page.EndCursor.isNull()) {return;}final ArrayList<ZLTextLineInfo> lineInfos = page.LineInfos;final int[] labels = new int[lineInfos.size() + 1];int x = getLeftMargin();int y = getTopMargin();int index = 0;int columnIndex = 0;ZLTextLineInfo previousInfo = null;for (ZLTextLineInfo info : lineInfos) {info.adjust(previousInfo);prepareTextLine(page, info, x, y, columnIndex);y += info.Height + info.Descent + info.VSpaceAfter;labels[++index] = page.TextElementMap.size();if (index == page.Column0Height) {y = getTopMargin();x += page.getTextWidth() + getSpaceBetweenColumns();columnIndex = 1;}previousInfo = info;}final List<ZLTextHighlighting> hilites = findHilites(page);x = getLeftMargin();y = getTopMargin();index = 0;for (ZLTextLineInfo info : lineInfos) {drawTextLine(page, hilites, info, labels[index], labels[index + 1]);y += info.Height + info.Descent + info.VSpaceAfter;++index;if (index == page.Column0Height) {y = getTopMargin();x += page.getTextWidth() + getSpaceBetweenColumns();}}//忽略部分代碼...
}
復(fù)制代碼
1.會(huì)獲取當(dāng)前設(shè)置的墻紙,如果能獲取到墻紙,那么會(huì)再去獲取墻紙的繪制方式,根據(jù)不同的方式,最終將墻紙繪制到bitmap上。
2.根據(jù)頁(yè)面Index,獲取對(duì)應(yīng)的page對(duì)象。
3.獲取到當(dāng)前要繪制的page對(duì)象后,通過(guò)preparePaintInfo方法,根據(jù)當(dāng)前page的PaintState,構(gòu)建頁(yè)面基礎(chǔ)元素信息,這里會(huì)給page設(shè)置size(可繪制區(qū)域?qū)捀咭约笆欠袷请p列繪制等)
private synchronized void preparePaintInfo(ZLTextPage page) {page.setSize(getTextColumnWidth(), getTextAreaHeight(), twoColumnView(), page == myPreviousPage);//忽略部分代碼...final int oldState = page.PaintState;final HashMap<ZLTextLineInfo,ZLTextLineInfo> cache = myLineInfoCache;for (ZLTextLineInfo info : page.LineInfos) {cache.put(info, info);}switch (page.PaintState) {default:break;case PaintStateEnum.TO_SCROLL_FORWARD://忽略部分代碼...break;case PaintStateEnum.TO_SCROLL_BACKWARD://忽略部分代碼...break;case PaintStateEnum.START_IS_KNOWN:if (!page.StartCursor.isNull()) {buildInfos(page, page.StartCursor, page.EndCursor);}break;case PaintStateEnum.END_IS_KNOWN://忽略部分代碼...break;}page.PaintState = PaintStateEnum.READY;// TODO: cache?myLineInfoCache.clear();if (page == myCurrentPage) {if (oldState != PaintStateEnum.START_IS_KNOWN) {myPreviousPage.reset();}if (oldState != PaintStateEnum.END_IS_KNOWN) {myNextPage.reset();}}
}
復(fù)制代碼
4.通過(guò)之前的分析,當(dāng)前頁(yè)面的PaintState在moveStartCursor時(shí)被設(shè)置為了START_IS_KNOWN,那么就會(huì)調(diào)用buildInfos方法,去構(gòu)建頁(yè)面原始數(shù)據(jù)信息:
private void buildInfos(ZLTextPage page, ZLTextWordCursor start, ZLTextWordCursor result) {result.setCursor(start);//將endcursor歸位于startcursorint textAreaHeight = page.getTextHeight();//獲取當(dāng)前頁(yè)面可繪制內(nèi)容區(qū)域高度page.LineInfos.clear();//清空之前構(gòu)建信息page.Column0Height = 0;//記錄第一列已構(gòu)建高度boolean nextParagraph;//是否是下一自然段ZLTextLineInfo info = null;//構(gòu)建的行內(nèi)容信息do {final ZLTextLineInfo previousInfo = info;resetTextStyle();final ZLTextParagraphCursor paragraphCursor result.getParagraphCursor();//獲取所構(gòu)建的段落對(duì)應(yīng)的cursorfinal int wordIndex = result.getElementIndex();//開(kāi)始的indexapplyStyleChanges(paragraphCursor, 0, wordIndex);info = new ZLTextLineInfo(paragraphCursor, wordIndex, result.getCharIndex(), getTextStyle());//構(gòu)建一個(gè)行信息final int endIndex = info.ParagraphCursorLength;//結(jié)束index(段落內(nèi)容長(zhǎng)度)while (info.EndElementIndex != endIndex) {info = processTextLine(page, paragraphCursor, info.EndElementIndex, info.EndCharIndex, endIndex, previousInfo);textAreaHeight -= info.Height + info.Descent;if (textAreaHeight < 0 && page.LineInfos.size() > page.Column0Height) {if (page.Column0Height == 0 && page.twoColumnView()) {textAreaHeight = page.getTextHeight();textAreaHeight -= info.Height + info.Descent;page.Column0Height = page.LineInfos.size();} else {break;}}textAreaHeight -= info.VSpaceAfter;result.moveTo(info.EndElementIndex, info.EndCharIndex);page.LineInfos.add(info);if (textAreaHeight < 0) {if (page.Column0Height == 0 && page.twoColumnView()) {textAreaHeight = page.getTextHeight();page.Column0Height = page.LineInfos.size();} else {break;}}}//如果當(dāng)前已經(jīng)讀取到了該段落最后位置,則獲取下一段落nextParagraph = result.isEndOfParagraph() && result.nextParagraph();if (nextParagraph && result.getParagraphCursor().isEndOfSection()) {if (page.Column0Height == 0 && page.twoColumnView() && !page.LineInfos.isEmpty()) {textAreaHeight = page.getTextHeight();page.Column0Height = page.LineInfos.size();}}} while (nextParagraph && textAreaHeight >= 0 &&(!result.getParagraphCursor().isEndOfSection() ||page.LineInfos.size() == page.Column0Height));resetTextStyle();
}private ZLTextLineInfo processTextLine(ZLTextPage page,ZLTextParagraphCursor paragraphCursor,final int startIndex,final int startCharIndex,final int endIndex,ZLTextLineInfo previousInfo
) {final ZLTextLineInfo info = processTextLineInternal(page, paragraphCursor, startIndex, startCharIndex, endIndex, previousInfo);if (info.EndElementIndex == startIndex && info.EndCharIndex == startCharIndex) {info.EndElementIndex = paragraphCursor.getParagraphLength();info.EndCharIndex = 0;// TODO: add error element}return info;
}private ZLTextLineInfo processTextLineInternal(ZLTextPage page,ZLTextParagraphCursor paragraphCursor,final int startIndex,final int startCharIndex,final int endIndex,ZLTextLineInfo previousInfo
){//忽略部分代碼...
}
復(fù)制代碼
已第一次閱讀時(shí)的構(gòu)建場(chǎng)景為例,通過(guò)buildInfos方法,針對(duì)要構(gòu)建內(nèi)容的page,會(huì)做如下幾件事:
- page的startCusor在之前被移動(dòng)到了第一自然段,并且第一自然段在創(chuàng)建時(shí)已讀取出來(lái)。在此方法中,會(huì)遍歷已讀取出的自然段內(nèi)容元素
- 遍歷元素過(guò)程中,會(huì)根據(jù)可繪制區(qū)域?qū)挾?#xff0c;一行一行的構(gòu)建出行元素信息,且每一行的高度為行內(nèi)元素中高度最高元素的高度
- 生產(chǎn)出的每一行元素,再根據(jù)可繪制區(qū)域高度,判斷該行是否能夠添加到頁(yè)面中。如果能,則加入并繼續(xù)構(gòu)建下一行;如果不能則退出構(gòu)建,當(dāng)前頁(yè)面元素構(gòu)建完畢
- 如果針對(duì)于第一自然段,遍歷完每一個(gè)元素,切構(gòu)建完每一行的行元素后,當(dāng)前仍有可用繪制高度,則獲取下一自然段,繼續(xù)重復(fù)上述步驟,構(gòu)建行信息,直至構(gòu)建結(jié)束
到此,已經(jīng)根據(jù)實(shí)際的可用空間,構(gòu)建出了當(dāng)前page的內(nèi)容數(shù)據(jù),并且是一行一行的內(nèi)容數(shù)據(jù)。每一行中,包含著之前讀取出的數(shù)據(jù)元素。
5.包裝元素,將元素轉(zhuǎn)變?yōu)榭梢员籧avas繪制的元素“區(qū)域”
經(jīng)過(guò)上面的頁(yè)面數(shù)據(jù)構(gòu)建,已經(jīng)將page當(dāng)前情況下的數(shù)據(jù)內(nèi)容一行行的構(gòu)建出來(lái)了。但是,目前構(gòu)建出來(lái)的數(shù)據(jù),還是只是數(shù)據(jù),而我們最終的目的是生成page對(duì)應(yīng)的bitmap。那么就需要對(duì)每一行的每一個(gè)元素進(jìn)行位置描述,轉(zhuǎn)變?yōu)轫?yè)面上一個(gè)一個(gè)的具有真實(shí)位置和數(shù)據(jù)信息的內(nèi)容。而這一步的轉(zhuǎn)變,是通過(guò)for遍歷每一行完成的:
x、y為元素繪制坐標(biāo)
for (ZLTextLineInfo info : lineInfos) {info.adjust(previousInfo);//將每一行中的每一個(gè)元素包裝為元素“區(qū)域”(帶有元素?cái)?shù)據(jù)和繪制坐標(biāo))prepareTextLine(page, info, x, y, columnIndex);y += info.Height + info.Descent + info.VSpaceAfter;labels[++index] = page.TextElementMap.size();if (index == page.Column0Height) {y = getTopMargin();x += page.getTextWidth() + getSpaceBetweenColumns();columnIndex = 1;}previousInfo = info;
}
復(fù)制代碼
6.繪制每一行的每一行元素“區(qū)域”
元素“區(qū)域”包裝完成,可以進(jìn)行繪制了:
for (ZLTextLineInfo info : lineInfos) {drawTextLine(page, hilites, info, labels[index], labels[index + 1]);y += info.Height + info.Descent + info.VSpaceAfter;++index;if (index == page.Column0Height) {y = getTopMargin();x += page.getTextWidth() + getSpaceBetweenColumns();}
}private void drawTextLine(ZLTextPage page, List<ZLTextHighlighting> hilites, ZLTextLineInfo info, int from, int to) {final ZLPaintContext context = getContext();final ZLTextParagraphCursor paragraph = info.ParagraphCursor;int index = from;final int endElementIndex = info.EndElementIndex;int charIndex = info.RealStartCharIndex;final List<ZLTextElementArea> pageAreas = page.TextElementMap.areas();if (to > pageAreas.size()) {return;}for (int wordIndex = info.RealStartElementIndex; wordIndex != endElementIndex && index < to; ++wordIndex, charIndex = 0) {final ZLTextElement element = paragraph.getElement(wordIndex);final ZLTextElementArea area = pageAreas.get(index);if (element == area.Element) {++index;if (area.ChangeStyle) {setTextStyle(area.Style);}final int areaX = area.XStart;final int areaY = area.YEnd - getElementDescent(element) - getTextStyle().getVerticalAlign(metrics());if (element instanceof ZLTextWord) {final ZLTextPosition pos =new ZLTextFixedPosition(info.ParagraphCursor.Index, wordIndex, 0);final ZLTextHighlighting hl = getWordHilite(pos, hilites);final ZLColor hlColor = hl != null ? hl.getForegroundColor() : null;drawWord(areaX, areaY, (ZLTextWord)element, charIndex, -1, false,hlColor != null ? hlColor : getTextColor(getTextStyle().Hyperlink));} else if (element instanceof ZLTextImageElement) {final ZLTextImageElement imageElement = (ZLTextImageElement)element;context.drawImage(areaX, areaY,imageElement.ImageData,getTextAreaSize(),getScalingType(imageElement),getAdjustingModeForImages());} else if (element instanceof ZLTextVideoElement) {//忽略部分代碼...} else if (element instanceof ExtensionElement) {//忽略部分代碼...} else if (element == ZLTextElement.HSpace || element == ZLTextElement.NBSpace) {//忽略部分代碼...}}}//忽略部分代碼...
}
復(fù)制代碼
7.繪制執(zhí)行者——ZLAndroidPaintContext
最終的繪制,是有此類對(duì)象來(lái)執(zhí)行,查看其主要的兩個(gè)方法:
public void drawString(int x, int y, char[] string, int offset, int length) {boolean containsSoftHyphen = false;for (int i = offset; i < offset + length; ++i) {if (string[i] == (char)0xAD) {containsSoftHyphen = true;break;}}if (!containsSoftHyphen) {myCanvas.drawText(string, offset, length, x, y, myTextPaint);} else {final char[] corrected = new char[length];int len = 0;for (int o = offset; o < offset + length; ++o) {final char chr = string[o];if (chr != (char)0xAD) {corrected[len++] = chr;}}myCanvas.drawText(corrected, 0, len, x, y, myTextPaint);}
}public void drawImage(int x, int y, ZLImageData imageData, Size maxSize, ScalingType scaling, ColorAdjustingMode adjustingMode) {final Bitmap bitmap = ((ZLAndroidImageData)imageData).getBitmap(maxSize, scaling);if (bitmap != null && !bitmap.isRecycled()) {switch (adjustingMode) {case LIGHTEN_TO_BACKGROUND:myFillPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));break;case DARKEN_TO_BACKGROUND:myFillPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));break;case NONE:break;}myCanvas.drawBitmap(bitmap, x, y - bitmap.getHeight(), myFillPaint);myFillPaint.setXfermode(null);}
}
復(fù)制代碼
8.paint方法前后bitmap內(nèi)容對(duì)比
起初bitmap:
paint方法執(zhí)行結(jié)束后bitmap:
至此,當(dāng)前page對(duì)應(yīng)的bitmap就準(zhǔn)備完成。通過(guò)bitmapmanager傳遞給ZLAndroidWidget,最終繪制此bitmap到控件上。
當(dāng)然,由于本人接觸此項(xiàng)目時(shí)間有限,而且書寫技術(shù)文章的經(jīng)驗(yàn)實(shí)在欠缺,過(guò)程中難免會(huì)有存在錯(cuò)誤或描述不清或語(yǔ)言累贅等等一些問(wèn)題,還望大家能夠諒解,同時(shí)也希望大家繼續(xù)給予指正。最后,感謝大家對(duì)我的支持,讓我有了強(qiáng)大的動(dòng)力堅(jiān)持下去。