安卓小说阅读器TextView实现思路

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HelloMyPeople/article/details/84947761

简介

最近在做一个安卓小说阅读器,相信刚开始做小说阅读器时,很多人都是采用安卓的TextView展示小说内容,我也是,我也发现了TextView作为中文小说阅读器文本展示Widget的不足之处:

  1. 虽然它遵循","(逗号)这些标点符号不在行开头的规定,但是当行尾空间“不太够”时,它会把前面一个字符“拖下水”,也就造成被连累字符的原本那行的行尾空了很多空间,虽然不违背规定,但是视觉上很不舒服。
  2. 很重要的一点,我们通过TextView无法获得到底有多少字符被展示了,这样我们就无法确定下一页需要展示的字符。
  3. 而且相信大家也发现追书神器APP(仅谈我用的版本,新版本应该修复了这个问题。。。吧),调节字体大小时,可以发现某些字体大小条件下,会出现末尾行显示不全的问题,难受。。。这也是直接用TextView会出现的严重问题

只说这三点不足,第一点其实可以勉强使用吧,但是2、3点却是致命不足。下面说说我是怎么解决这些问题的。

寻找办法

  1. 首先我当然是想找现成的啦,可惜找遍了百度和谷歌,没有相关描述(只有一些误导性的博客文章),因为很多小说阅读器都是运营小说产品的公司开发的,很多有名的小说产品APP都做到了很优秀的阅读效果,当然也解决了上面的问题,但是由于商业原因,他们肯定不会公开解决方案的。
  2. 在github上找到一个开源项目,叫“任阅”,下载了demo,发现就是追书神器的设计,而且阅读器非常卡,综合我之前的追书神器体验,我肯定不会直接用这个的源码。但是我很好奇它是不是用TextView实现的,最后发现,没有用,而是自定义View。
  3. 因为我并不是专门编写安卓程序的,也没有写过多少自定义View,不会超过10个,所以我决定看看TextView的文档,看看是否有可供使用的方法,但是我看了很久,没有。。。只能自定义View。
  4. 现在明确了方向——自定义View,那么开始写View吧。

自定义View的编写

  1. 显然我们需要自己绘制文本,那么很简单,接触过自定义View的朋友都知道,View的onDraw方法提供的Canvas对象有一种方法叫drawText,简单直接。简述一下绘制过程:
    1. 由于drawText方法不会自动换行,所以需要手动,把需要绘制的Stringsplit方法根据换行符分割成若干行
    2. 接下来需要用到一个很重要的方法Paint.breakText,可以自行学习一下这个方法,它的功能是给定一个字符串和最大宽度,它将会返回在这个情况下它最多能绘制字符的数量。这样我们获得这个数量后就可以轻松的实现分行了。而之前split分到的若干行其实更准确的应该叫做若干段(落)
    3. 显然我们能够很容易知道到哪便应该停止绘制字符,绘制了多少个字符,于是解决了之前的不足的2、3两点。
    4. 标点符号问题的解决其实也很简单,我们通过breakText得知可以绘制n个字符时,我们应该判断下一个字符(也就是将会是下一行的首个字符)是不是应该继续draw的标点符号,如果是,我们令n++,也就是再多draw一个字符。
    5. 其实实现了前面几点还不行,因为这样仅仅比纯文本的TextView功能好那么一点,还有字符排布。我把前四点都实现好了,一运行,呵呵,和小米阅读APP的相比(为什么不用荣耀阅读。。。因为我的手机是小米,自带的),惨不忍睹啊。我的阅读器行尾都不能对齐。我马上想到了和CSS差不多的方法:letterSpacing字符间距。好,让不足的都让字符间距给补上了,我一开始相信小米阅读也是这么做的。什么?API21及以上才能有Paint.setLetterSpacing,麻蛋,不管了,不能覆盖100%的用户也不要紧,然后赶紧做好进行测试,谁知道最后依然是没对齐,总是差8px左右。我怀疑是间距计算不够精确,因为Paint.setLetterSpacing只接受em(1em=fontSize)单位,麻蛋。我开始想办法提高精确度,用二分法逼近误差最小值,但是测试发现,依然好不了多少。好在不久就想到了是间距大多数情况无法均分造成的,比如说,差33px,有13个字符,于是有12个空位,平均得到的是33/12=2(整数除法),那么其实差了33-12*2=9px,于是某些间距必须大一点,某些小一点,在这个例子,我们可以让前9个为3px,后3个为2px,这样就刚刚好把33px的空白分到了字符间了,排布更合理。
  2. 总结:
    1. drawText绘制字符
    2. split分段,对于每一段用breakText计算能容纳最大字数分行
    3. breakText分行后应当检查下一个字符是否为不能出现在行头的字符,如果不能,硬塞也要塞到当前行。
    4. 绘制的时候应当合理排布,此时没有现成的方法,只能自己一个一个字符绘制,自己分配好drawText的横坐标。

误区

有些人以为计算TextView的字符数很简单,在没有空白符的时候可以直接width/fontSize*lineNum什么的,显然这是对fontSize的误区,fontSize在很多情况下并不是字符的宽,也不是高,每个字符都可能不一样,与字符本身有关,也与字体(TypeFace)有关,所以我们需要趁绘计数,而测量字体宽度的方法有Paint.measureTextPaint.breakText,测量字体Bound的有Paint.getTextBounds

最后

没有代码,因为我自己在写一个小说阅读器项目,虽然是私人的,但是还是不太好公布,请见谅。

虽然没办法公布代码,但是上面已经介绍了大部分逻辑了,可以轻易写出,毕竟我也是昨天从无到有写好的。

文本展示效果和小米阅读的TextView几乎一样(仅仅是我的渲染速度快点,因为小米的还有其它东西,嵌入一些评论。。。),测试过后能良好运行。

展开阅读全文

没有更多推荐了,返回首页