【鸿蒙NEXT】从零开始在鸿蒙Next上实现小说阅读器——分页计算的实现 ...

打印 上一主题 下一主题

主题 975|帖子 975|积分 2935

实现一个小说阅读器的第一个核心环节就是对小说内容的分页计算,实现这个计算的心酸泪就不提了,直接上正文。
起首还是放上结论:
如果仅仅从笔墨测绘的性能方面来说,鸿蒙在这方面的能力跟flutter与原生方面差距并不大,但是由于鸿蒙目前提供的测绘API过于基础,并不支持返回测量的具体效果,也不支持根据给定区域测量承载多少内容,以是目前的测量方式肯定有着巨大的优化空间,但是从效果上来说好像至少可以用了
以《大爱仙尊》的第一章为例:
其计算耗时在一帧左右,如果从秒开方面来说问题并不大,后续章节的计算可以放到多线程中慢慢加载;
内容计算准确性方面也没有太多问题:

分析与筹划

小说分页计算的核心主要是计算单页能展示多少字,在这里还是沿用之前的设定,对小说内容进行分章节分段落处理后,再对章节内容进行计算;
按照从前搞阅读器的方案,这里需要做的事有这么几件:

  • 整本小说内容的异步加载。
  • 小说内容的分章节分段落处理。
  • 对已经分好章节的内容进行分页计算处理。
看上去是挺简朴的?
实现

这里先不提分章节处理的部门(由于目前实现的分章节计算方式性能太差了……让我不禁想起了当时leetCode刷题的大数和大量数字的处理方案……),先从分页计算的部门开始。
   从API的层面来说,目前基于的12API并未与现在的11的基础上有所不同,这块因此可以直接在API11上利用;
  起首在Android和Flutter上,对于这块的处理方式其实没啥麻烦的,Android 的staticLayout本身就能自动实现包括换行规则在内的多行文本计算,android上乃至可以通过StaticLayout直接在canvas上绘制。
但在鸿蒙Next上并没有这种东西,来到文档,关于笔墨的测量,其实就两个API:


第二个写的描述还有点问题……
正如其介绍所述,仅仅能得知高度和宽度,
以是问题来了,得知每页上体现的笔墨数目?
大概有人以为直接把所有笔墨的宽度都设置为一个固定长度,直接按全角计算的方式不就完事了?
这种方式虽然不再需要测量,但是忽视了一个问题:如何处理换行规则?对于需要提前换行的英文单词、字符等一系列都不能做到处理。
以是最终,如果不想自己实现一套符合华为笔墨组件的计算算法,只能在鸿蒙的API中寻找答案了:
认识android中StaticLayout的小伙伴应该都知道一个API:BreakIterator 这个就是android中实现换行计算的基础,所幸的是,在鸿蒙上还是有这个API:BreakIterator
这样关于换行规则的实现,至少有了获取方式。最后就是如何结合这个API来计算每页的笔墨数目了:
这块我目前的实现方式是这样:
  1. calculateForLine(paragraph: string[], targetHeight: number, targetWidth: number, startIndex: number,
  2.   wordSize: SizeOptions): Promise<string[]> {
  3.   return new Promise((resolve, reject) => {
  4.     let constraintWidth = targetWidth;
  5.     let wordWidth: number = typeof wordSize.width === 'number' ? (wordSize.width ?? 0) : 0
  6.     let lineHeight: number = typeof wordSize.height === 'number' ? (wordSize.height ?? 0) : 0
  7.     let guessLineWordCount = Math.floor(constraintWidth / wordWidth)
  8.     let maxLines = Math.floor(targetHeight / lineHeight);
  9.     // 断句对象
  10.     let breakIterator = I18n.getLineInstance('cn');
  11.     let result: string[] = [];
  12.     let currentLineIndex = 0;
  13.     while (currentLineIndex < maxLines - 1 && paragraph.length != 0) {
  14.       let currentTarget = paragraph[0]
  15.       if (currentTarget.length < guessLineWordCount) {
  16.         result[currentLineIndex] = currentTarget;
  17.         currentLineIndex++
  18.         paragraph.splice(0, 1)
  19.       } else {
  20.         let tempCurrentTarget = currentTarget;
  21.         let lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;
  22.         if (lineCount == 1) {
  23.           result[currentLineIndex] = tempCurrentTarget;
  24.           currentLineIndex++
  25.           paragraph.splice(0, 1)
  26.         } else {
  27.           breakIterator.setLineBreakText(currentTarget)
  28.           breakIterator.following(guessLineWordCount)
  29.           breakIterator.previous();
  30.           tempCurrentTarget = currentTarget.substring(0, breakIterator.current())
  31.           lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;
  32.           if (lineCount > 1) {
  33.             while (lineCount > 1) {
  34.               tempCurrentTarget = currentTarget.substring(0, breakIterator.previous())
  35.               lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;
  36.             }
  37.             result[currentLineIndex] = tempCurrentTarget;
  38.             currentLineIndex++
  39.             paragraph[0] = paragraph[0].substring(tempCurrentTarget.length, paragraph[0].length)
  40.           } else {
  41.             while (lineCount <= 1) {
  42.               tempCurrentTarget = currentTarget.substring(0, breakIterator.next())
  43.               lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;
  44.             }
  45.             tempCurrentTarget = currentTarget.substring(0, breakIterator.previous())
  46.             result[currentLineIndex] = tempCurrentTarget
  47.             currentLineIndex++
  48.             paragraph[0] = paragraph[0].substring(tempCurrentTarget.length, paragraph[0].length)
  49.           }
  50.         }
  51.       }
  52.     }
  53.     resolve(result)
  54.   });
  55. }
复制代码
虽然看上去乱七八糟的,其实实现思路很简朴:

  • 先计算出一个笔墨的宽度,结合展示区域宽度,预测一下一行多少个笔墨
  • 结合展示区域高度和自定义段落规则(这部门在代码中还未实现,目前只是简朴的计算了最大行数),来循环计算每个段落的高度,直到达到最大高度。
  • 在循环中,如果当前段落的长度还不如预测长度,那么肯定他填不满一行,不用测量直接参加当前页面数据中。
  • 如果当前段落的长度多于预测长度,那么先测量一次,如果行数为1,那么直接参加到当前页面数据中。
  • 如果测量的效果大于1,那么说明需要确定分行位置,我会将BreakIterator设置预测的位置,然后分行位置的测量行数如果大于1,那么就不绝调用breakIterator.previous(),来确定出现分行的位置。如果小于1,说明分行位置在一行内,需要反之不绝调用breakIterator.next()来确定分行的位置。
  • 最终如果达到了最大高度,段落内容仍未计算完,那么说明当前页承载不下当前段落,需要对段落进行截取,放到下一次的分页计算中处理。
这样,就可以计算出一页中一行的笔墨数目,将其join一下放到Text中,就是动图中的效果了。
结论和说明:

可以看出,上述环节的第五步需要不绝循环计算,天然这里有着巨大的优化空间,这也是开头提到的部门,如果鸿蒙能提供一种直接计算出每行信息的API,这里的性能提升将是巨大的。现在的方式虽然保证了正确性,但是性能方面只能说差能人意。
不知道各位大佬们有没有什么好的方案?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表