【HarmonyOS应用开发】在鸿蒙上实现小说阅读器——分页计算的实现

avatar
作者
筋斗云
阅读量:5

实现一个小说阅读器的第一个核心环节就是对小说内容的分页计算,实现这个计算的心酸泪就不提了,直接上正文。

首先还是放上结论:

如果仅仅从文字测绘的性能方面来说,鸿蒙在这方面的能力跟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来计算每页的文字数量了:

这块我目前的实现方式是这样:

calculateForLine(paragraph: string[], targetHeight: number, targetWidth: number, startIndex: number,   wordSize: SizeOptions): Promise<string[]> {   return new Promise((resolve, reject) => {     let constraintWidth = targetWidth;      let wordWidth: number = typeof wordSize.width === 'number' ? (wordSize.width ?? 0) : 0     let lineHeight: number = typeof wordSize.height === 'number' ? (wordSize.height ?? 0) : 0      let guessLineWordCount = Math.floor(constraintWidth / wordWidth)     let maxLines = Math.floor(targetHeight / lineHeight);      // 断句对象     let breakIterator = I18n.getLineInstance('cn');      let result: string[] = [];      let currentLineIndex = 0;      while (currentLineIndex < maxLines - 1 && paragraph.length != 0) {       let currentTarget = paragraph[0]       if (currentTarget.length < guessLineWordCount) {         result[currentLineIndex] = currentTarget;         currentLineIndex++         paragraph.splice(0, 1)       } else {          let tempCurrentTarget = currentTarget;          let lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;          if (lineCount == 1) {           result[currentLineIndex] = tempCurrentTarget;           currentLineIndex++           paragraph.splice(0, 1)         } else {           breakIterator.setLineBreakText(currentTarget)           breakIterator.following(guessLineWordCount)           breakIterator.previous();           tempCurrentTarget = currentTarget.substring(0, breakIterator.current())           lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;             if (lineCount > 1) {             while (lineCount > 1) {               tempCurrentTarget = currentTarget.substring(0, breakIterator.previous())               lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;             }             result[currentLineIndex] = tempCurrentTarget;             currentLineIndex++             paragraph[0] = paragraph[0].substring(tempCurrentTarget.length, paragraph[0].length)           } else {             while (lineCount <= 1) {               tempCurrentTarget = currentTarget.substring(0, breakIterator.next())               lineCount = this.measureText(tempCurrentTarget, constraintWidth) / lineHeight;             }             tempCurrentTarget = currentTarget.substring(0, breakIterator.previous())             result[currentLineIndex] = tempCurrentTarget             currentLineIndex++             paragraph[0] = paragraph[0].substring(tempCurrentTarget.length, paragraph[0].length)           }         }       }     }      resolve(result)   }); } 

虽然看上去乱七八糟的,其实实现思路很简单:

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

这样,就可以计算出一页中一行的文字数量,将其join一下放到Text中,就是动图中的效果了。

结论和说明:

可以看出,上述环节的第五步需要不断循环计算,自然这里有着巨大的优化空间,这也是开头提到的部分,如果鸿蒙能提供一种直接计算出每行信息的API,这里的性能提升将是巨大的。现在的方式虽然保证了正确性,但是性能方面只能说差强人意。

写在最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

在这里插入图片描述

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

在这里插入图片描述

《鸿蒙生态应用开发V2.0白皮书》

在这里插入图片描述

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

在这里插入图片描述

《鸿蒙开发基础》

●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
在这里插入图片描述

《鸿蒙开发进阶》

●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
在这里插入图片描述

《鸿蒙进阶实战》

●ArkTS实践
●UIAbility应用
●网络案例
……
在这里插入图片描述

获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!