第九章、epub文件处理 -- 显示.xhtml文件


第九章、epub文件处理 -- 显示.xhtml文件


经历了“千难万险”,我们终于进入了 XML 文件处理的最后部分 ---- xhtml 文件的内容在屏幕上显示出来。本章将详细介绍显示的流程。


显示的流程是从ZLAndroidWidget类的onDraw方法开始的。

这个流程主要是通过Canva类对一个新建的Bitmap类进行处理,这种处理相当于在一块“画布”上按照应有的格式将字一个一个“画”到“画布”上去,最后将画布显示到屏幕上。这个“画”的动作反映到代码,主要涉及ZLTextView类的三个方法:preparePaintInfo方法、prepareTextLine方法、drawTextLine方法。

preparePaintInfo方法从定位指定段落后得到的ZLTextPage类中取出一个一个取出代表段落中每个字的ZLTextElement子类,计算出屏幕上的每一行对应着哪些字。

PS:获取ZLTextElement子类的过程是第八章中介绍的“ZLTextElement子类 -> ZLTextParagraphCursormyElements属性 -> ZLTextPageStartCursor属性”的逆推过程,可以互相参考)

prepareTextLine方法进一步获得每一行中的每一个字的位置信息与样式信息,代码会用这些信息新建一个ZLTextElementArea类,并将所有新建的ZLTextElementArea类加入到ZLTextPage类的TextElementMap属性

drawTextLine方法会根据ZLTextWord类中的文本信息与ZLTextElementArea类中的位置信息调用Canvas类的drawText方法对Bitmap类进行操作,最终将字一个一个“画”到“画布”上去


回顾

在开始详细介绍显示流程之前,让我们先回顾下我们已经走过的“千难万险”。

FBReader程序启动时,代码会建立一个子线程。

第一章与第二章中,我们将注意力放在主线程上,介绍了主线程是如何控制一个进度条的显示和消失,并通过解析资源文件在进度条上显示合适的文字。

第三章一直到第八种,我们就开始把注意力集中到了读取epub文件的子线程(子线程的代码如下图)。

第三章中我们介绍了LibrarygetRecentBook方法是如何获取包括文件路径在内等书籍信息的。

第五章到第八章,我们介绍了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类,这可以类比为新建了一个新的“画布”,然后会调用ZLAndroidWidget类drawOnBitmap方法,将要显示的部分“画”到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类对应的缓存就会被显示显示在屏幕上。相当于,把已经“画”好的“画布”显示到屏幕上去。


转载于:https://my.oschina.net/u/938986/blog/335940

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值