第九章、epub文件處理 -- 顯示.xhtml文件


第九章、epub文件處理 -- 顯示.xhtml文件


經歷了「千難萬險」,咱們終於進入了 XML 文件處理的最後部分 ---- xhtml 文件的內容在屏幕上顯示出來。本章將詳細介紹顯示的流程。


顯示的流程是從ZLAndroidWidget類的onDraw方法開始的。 html

這個流程主要是經過Canva類對一個新建的Bitmap類進行處理,這種處理至關於在一塊「畫布」上按照應有的格式將字一個一個「畫」到「畫布」上去,最後將畫布顯示到屏幕上。這個「畫」的動做反映到代碼,主要涉及ZLTextView類的三個方法:preparePaintInfo方法、prepareTextLine方法、drawTextLine方法。 android

preparePaintInfo方法從定位指定段落後獲得的ZLTextPage類中取出一個一個取出表明段落中每一個字的ZLTextElement子類,計算出屏幕上的每一行對應着哪些字。 數組

PS:獲取ZLTextElement子類的過程是第八章中介紹的「ZLTextElement子類 -> ZLTextParagraphCursormyElements屬性 -> ZLTextPageStartCursor屬性」的逆推過程,能夠互相參考) 緩存

prepareTextLine方法進一步得到每一行中的每個字的位置信息與樣式信息,代碼會用這些信息新建一個ZLTextElementArea類,並將全部新建的ZLTextElementArea類加入到ZLTextPage類的TextElementMap屬性 app

drawTextLine方法會根據ZLTextWord類中的文本信息與ZLTextElementArea類中的位置信息調用Canvas類的drawText方法對Bitmap類進行操做,最終將字一個一個「畫」到「畫布」上去 動畫


回顧

在開始詳細介紹顯示流程以前,讓咱們先回顧下咱們已經走過的「千難萬險」。

FBReader程序啓動時,代碼會創建一個子線程。 ui

第一章與第二章中,咱們將注意力放在主線程上,介紹了主線程是如何控制一個進度條的顯示和消失,並經過解析資源文件在進度條上顯示合適的文字。 spa

第三章一直到第八種,咱們就開始把注意力集中到了讀取epub文件的子線程(子線程的代碼以下圖)。
線程

第三章中咱們介紹了LibrarygetRecentBook方法是如何獲取包括文件路徑在內等書籍信息的。 xml

第五章到第八章,咱們介紹了FBReaderAppopenBookInternal方法是如何讀取epub文件的。讀取epub文件的流程主要由三個方法構成:BookModel類的createModel方法,FBView類的setModel方法以及ZLAndroidWidget類的repaint方法。

BookModel類的createModel方法首先解析了epub文件內部的container.xml.opf文件(第五章中介紹)。而後,又利用獲得的信息解析了.xhtml文件。在這個解析過程當中,.xhtml文件的內容首先被轉換爲一個包含文本信息和標籤信息的char數組(第六章介紹)。

FBView類的setModel方法又將.xhtml文件中要被顯示的部分又從char數組轉換成了一個由ZLTextElement子類組成的ArrayList

ZLAndroidWidget類的repaint方法則在最後會觸發了顯示流程,將以前獲得的解析數據在屏幕上顯示出來。

    


回顧完畢,下面就正式開始本章的內容了。

FBReader程序的主界面就是ViewZLAndroidWidget類,咱們能夠layout文件夾下的main.xml看到這個類


ZLAndroidWidget類的onDraw方法

ZLAndroidWidget類的repaint方法會調用ZLAndroidWidget類的onDraw方法。當ZLAndroidWidget類的onDraw方法被調用時,代碼會進行一個判斷,看當前是否處於翻頁動畫中。若是當前處在翻頁動畫中,那麼就調用ZLAndroidWidget類的onDrawInScrolling方法(翻頁動畫的原理其實就是不斷得在屏幕上畫兩個頁面,一個頁面一直不動,另外一個頁面則不斷向左邊或右邊移動,這個部分以後會有章節專門介紹)。若是不在翻頁動畫中,那麼就調用ZLAndroidWidget類的onDrawStatic方法。初次打開程序的時候,無疑必定是不會在翻頁動畫中的。


ZLAndroidWidget類的onDrawStatic

onDrawStatic方法中調用了Canvas類的drawBitmap方法。drawBitmap方法會要求一個Bitmap類做爲參數。Bitmap類對應的是一段緩存,這段緩存最終會被顯示在屏幕上。代碼會把屏幕上須要顯示的內容寫入這段內存。

BitmapManager類的getBitmap方法:

這個方法會首新建一個Bitmap類,這能夠類比爲新建了一個新的「畫布」,而後會調用ZLAndroidWidgetdrawOnBitmap方法,將要顯示的部分「畫」到Bitmap類表明的「畫布」。

(PS:這裏的「畫布」只是一種比喻,其實直譯爲「位圖」可能更合適。)

ZLAndroidWidgetdrawOnBitmap方法:

android程序是不直接對Bitmap類進行操做的,而是經過Canva類來對Bitmap類進行操做。因此這個方法中以Bitmap類爲參數新建一個Canvas類。接着,代碼就調用了ZLTextView類的paint方法

ZLTextView類的paint方法中,本章中最終要的三個方法將悉數登場:preparePaintInfo方法、prepareTextLine方法、drawTextLine方法,下面咱們將一個一個來介紹。


ZLTextView類preparePaintInfo方法

進入這個方法會對ZLTextPage類的PaintState屬性進行判斷。

這個屬性咱們在ZLTextPage類的moveStartCursor方法中已經設置過了。(第八章「epub文件處理 -- 定位指定段落」中曾經介紹過這個方法)

PSSTART_IS_KNOWN這個值能夠理解爲,已經定位到當前要顯示的段落,當前段落第一個字在char數組的偏移量已經得到)

    


    根據這個PaintState屬性的值,咱們就進入了ZLTextView類 buildInfos方法
    

    ZLTextView類 buildInfos方法

        buildInfos方法的任務是計算屏幕上一共能顯示多少行。

        這個方法會用textAreaHeight變量記錄屏幕能顯示的總高度(778行),同時代碼會不斷在textAreaHeight變量中減去每一行的高度(798行)。當textAreaHeight變量小於0時,就表明屏幕已經被填充滿了,同時也就知計算除了了一屏總共能夠顯示多少行(800行)。

        
        若是當前段落的內容被所有讀取完時,代碼會調用ZLTextWordCursor類的nextParagraph方法自動定位到下一個段落。ZLTextWordCursor類的nextParagraph方法是經過ZLTextParagraphCursor類中的cursor方法完成定位到下一段落的(第八章一整個章節介紹了經過ZLTextParagraphCursor類中的cursor方法定位到指定段落的流程)。

        

        PSZLTextView類 buildInfos方法中還調用了ZLTextViewBase類的resetTextStyle方法,這個方法涉及樣式文件的處理流程,請參考第十章。

    ZLTextView類 processTextLine方法

        咱們曾經在「定位指定段落」的流程中獲得過一個表明當前段落的ArrayList(參考第八章)。processTextLine方法的任務就是計算出屏幕上每一行中的第一個字與最後一個字在這個ArrayList中的位置。這些信息會生成一個ZLTextPage類。最終,表明屏幕上每一行的ZLTextPage類會被加入到ZLTextPage類的LineInfos屬性中去。

        ZLTextLineInfo類中的RealStartElementIndex屬性就表明了每一行第一個字在ArrayList中的位置,EndElementIndex屬性就表明了每一行最後一個字在ArrayList中的位置

        

        processTextLine方法中,代碼首先新建一個ZLTextLineInfo類,來表明這一行的信息。同時,當前在ArrayList中的偏移量startIndex會被存儲到RealStartElementIndex屬性中去,用來表明這一行的第一個字的位置。(請對比接下來會介紹的「ZLTextWordCursor類的moveTo方法、getElementIndex方法配合,保證了每一行的最後一個字就是下一行的第一個字」部分)

        

        接着,代碼會用屏幕總寬度減去每行的右縮進信息來得到每行能顯示的最大寬度,這個最大寬度就是maxWidth變量

        而後,代碼會經過一個遞增currentElementIndex變量,從ZLTextParagraphCursor類中表明當前段落的ArrayList中依次讀取元素

         

        若是代碼讀取到表明標籤的ZLTextControlElement類,就會應用當前標籤對應的樣式

        

        getElementWidth方法當前應用的樣式計算出每一個字將佔的寬度,並將每一個字的寬度累加

        

        當累加的字符長度大於屏幕能顯示的寬度時,就表明這一行被填充滿了。此時,咱們就能計算出這一行的最後一個字位於ArrayList中的哪一個位置,並將位置信息存儲到ZLTextLineInfo類中的EndElementIndex屬性。

        

        ZLTextWordCursor類的moveTo方法、getElementIndex方法配合,保證了每一行的最後一個字就是下一行的第一個字。

        

        ZLTextWordCursor類的moveTo方法:

         

        ZLTextWordCursor類的getElementIndex方法:

若是代碼判斷當前的位置是位於段落起始位置的話,還會作一些額外的工做:

1、讀取位於段落開頭表明標籤信息的ZLTextControlElement類,得到標籤對應的樣式

2、跳過表明標籤信息的ZLTextControlElement類,獲取改行的表明第一個字的ZLTextWord類在表明當前段落的ArrayList中的偏移量

3、獲取首行的左縮進信息

最後,表明每一行的ZLTextLineInfo類將被加入到ZLTextPage類的LineInfos屬性中去。




ZLTextViewprepareTextLine方法

    這個方法進一步計算出每一行中的每個字在屏幕上的絕對位置,每一個字的絕對位置以及顯示格式等信息會用一個ZLTextElementArea類表示

    首先,ZLTextView類的paint方法會經過for循環從ZLTextPage類的LineInfos屬性中取出表明每一行的ZLTextLineInfo類,對每一行調用ZLTextView類的prepareTextLine方法

    

    在進入for循環以前,代碼會首先利用FBView類的getTopMargin方法獲取頂部的頁邊距,這個頂部頁邊距會被當作屏幕上第一行的y座標

    

    而後在for循環中,代碼會累加每行的行高,以獲取下一行的y座標

    

    代碼會帶着每一行的y座標進入ZLTextView類的prepareTextLine方法。

    

    在prepareTextLine方法中,代碼首先會得到當前行的y座標,當前行的每一個字都適用這個y座標。

    

    接着,代碼從表明當前行的ZLTextLineInfo類中的EndElementIndex屬性得到當前最後一個字的位置,又從RealStartElementIndex屬性中得到了當前行第一個字的位置。而後利用for循環讀取當前行第一個字到最後一個字之間的內容。

    

    在這個循環中,根據當前行左邊的頁邊距與左縮進信息得到當前行第一個字的x座標

    

    接着,代碼會依次獲取當前行中每一個字應占的寬度

    

    而後累加每一個字的寬度,以獲取下一個字的x座標

    

    若是讀取的過程當中,讀取到了表明標籤信息的ZLTextControlElement類,就調用ZLTextViewBase類的applyControl方法應用樣式(應用樣式的流程參考第十章)。

    

    代碼會根據每一個字x座標、y座標的位置信息以及適用的樣式生成一個ZLTextElementArea類(1071行),接着代碼會將新建的ZLTextElementArea類加入ZLTextPage類的TextElementMap屬性中。

    

    (代碼會根據每一個字x座標、y座標的位置信息以及適用的樣式生成一個ZLTextElementArea類(1071行),接着代碼會將新建的ZLTextElementArea類加入ZLTextPage類的TextElementMap屬性中。

    


ZLTextViewdrawTextLine方法

這個方法根據ZLTextWord類中的文本信息與ZLTextElementArea類中的位置信息調用Canvas類的drawText方法對Bitmap類進行操做,最終將字一個一個「畫」到「畫布」上去

首先,ZLTextView類的paint方法會再一次經過for循環從ZLTextPage類的LineInfos屬性中取出表明每一行的ZLTextLineInfo類,對每一行調用ZLTextView類的drawTextLine方法

ZLTextView類的drawTextLine方法中,代碼會得到對應當前字的ZLTextWord類和ZLTextElementArea類。而後,代碼會根據ZLTextWord類中的文本信息與ZLTextElementArea類中的位置與樣式信息調用Canvas類的drawText方法,將當前的字「畫」到「畫布」上



preparePaintInfoprepareTextLinedrawTextLine三個方法所有調用完畢,代碼會回到ZLAndroidWidget類的onDrawStatic方法,將已經設置好的Bitmap類做爲參數調用Canva類的drawBitmap方法。這樣,Bitmap類對應的緩存就會被顯示顯示在屏幕上。至關於,把已經「畫」好的「畫布」顯示到屏幕上去。

相關文章
相關標籤/搜索