【HarmonyOS】实现分页滚动文本组件:为何选择 Scroll + Text 而非 textOverflow
import { promptAction } from '@kit.ArkUI'
@Entry
@Component
struct Page37 {
@State lineHeight: number = 0 // 单行文本的高度
@State pageHeight: number = 0 // 每页的最大高度
@State totalContentHeight: number = 0 // 整个文本内容的高度
@State textContent: string = " " // 文本内容,默认一个空格是为了计算单行文本的高度
@State scrollOffset: number = 0 // 当前滚动偏移量
@State totalPages: number = 1 // 总页数
@State currentPage: number = 1 // 当前页数
scroller: Scroller = new Scroller() // 滚动条实例
resetMaxLineHeight() {
if (this.lineHeight > 0 && this.pageHeight > 0 && this.totalContentHeight > 0) {
this.pageHeight = (Math.floor(this.pageHeight / this.lineHeight)) * this.lineHeight
this.totalPages = Math.ceil(this.totalContentHeight / this.pageHeight) //向上取整得到总页数
}
}
build() {
Column() {
Text('第一章')
.margin({ top: 10, bottom: 10 })
.backgroundColor(Color.Pink)
.width('100%')
.textAlign(TextAlign.Center)
Column() {
Scroll(this.scroller) {
Column() {
Text(this.textContent)
.backgroundColor(Color.Orange)
.fontSize(20)
.lineHeight(40)
.fontColor(Color.Black)// .textOverflow({ overflow: TextOverflow.Clip })
.margin({ top: this.scrollOffset })
.onAreaChange((oldArea: Area, newArea: Area) => {
if (this.lineHeight == 0 && newArea.height > 0) {
this.lineHeight = newArea.height as number
this.resetMaxLineHeight()
//添加数据测试
let str = ""
for (let i = 1; i <= 20; i++) {
str += ` ${i}、荣誉和耻辱,是荣辱观中的一对基本范畴,是指社会对人们行为褒贬评价以及人们对这种评价的自我感受。知荣辱,是人性的标志,是人区别于动物、人之为人的重要标准。`
}
this.textContent = str
return
}
if (this.totalContentHeight != newArea.height) {
console.info(`newArea.height:${newArea.height}`)
this.totalContentHeight = newArea.height as number
this.resetMaxLineHeight()
}
})
}.hitTestBehavior(HitTestMode.Block) //禁止滑动
}.scrollBar(BarState.Off)
.constraintSize({ maxHeight: this.pageHeight == 0 ? 1000 : this.pageHeight })
}
.width('100%')
.layoutWeight(1)
.onAreaChange((oldArea: Area, newArea: Area) => {
if (this.pageHeight == 0 && newArea.height > 0) {
this.pageHeight = newArea.height as number
this.resetMaxLineHeight()
}
})
Row() {
Button('上一页').onClick(() => {
if (this.currentPage == 1) {
promptAction.showToast({ message: "没有上一页了" })
return;
}
this.scrollOffset += this.pageHeight
this.currentPage--;
})
Text(`${this.currentPage}/${this.totalPages}`)
Button('下一页').onClick(() => {
if (this.currentPage == this.totalPages) {
promptAction.showToast({ message: "没有下一页了" })
return;
}
this.scrollOffset -= this.pageHeight
this.currentPage++;
})
}.margin({ top: 10, bottom: 10 }).backgroundColor(Color.Pink).width('100%').justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.height('100%')
.backgroundColor(Color.Gray)
}
}
【实现思路】
目标是实现在HarmonyOS应用中的分页滚动文本效果,使得用户能够通过“上一页”和“下一页”按钮来浏览不同的页面。我们选择使用 Scroll 组件结合 Text 组件来实现这一功能,而不是采用 textOverflow 的方式,原因在于 textOverflow 无法直接获取到文本控件被截断后的内容。
具体实现过程如下:
1. 初始化状态:
利用 @State 装饰器定义状态变量来存储单行文本的高度 (lineHeight)、每页的最大高度 (pageHeight)、文本内容的总高度 (totalContentHeight)、文本内容 (textContent)、滚动偏移量 (scrollOffset)、总页数 (totalPages) 和当前页数 (currentPage)。
2. 计算单行高度:
通过监听 Text 组件的 onAreaChange 事件,当首次获取到文本元素的高度时,将其赋值给 lineHeight 并调用 resetMaxLineHeight 方法来计算每页的最大高度。
3. 生成内容:
初始时,textContent 中包含一个空格,以便能够计算出单行文本的高度。一旦单行高度计算完成,通过循环生成多个段落填充文本内容。
4. 分页逻辑:
① 当 totalContentHeight 发生变化时,调用 resetMaxLineHeight 方法更新总页数。
② “上一页”和“下一页”按钮通过修改 scrollOffset 和 currentPage 来实现翻页效果。
5. UI 布局与滚动控制:
① 使用 Column 和 Row 布局来组织界面元素。
② Scroll 组件用于创建滚动区域,而 Text 组件则用于显示文本内容。
③ 通过设置 hitTestBehavior 为 HitTestMode.Block 来阻止文本区域的滑动行为,确保滚动仅发生在父级滚动区域中。
6. 适配不同屏幕尺寸:
① 为了确保组件在不同设备上的表现一致,可以考虑使用百分比布局或者动态计算容器尺寸的方法来适应不同屏幕尺寸。
② 通过设置 Scroll 组件的 constraintSize 属性,限制其最大高度为 pageHeight 或默认值 1000,以确保内容不会超出当前页面的高度。
7. 动态计算内容高度:
① 通过监听 Scroll 组件的 onAreaChange 事件,当容器高度发生变化时,重新计算 pageHeight 和 totalPages。
② 这样可以确保组件能够动态地适应不同的屏幕尺寸和内容长度,避免内容溢出或遮挡问题。
【为何不使用 textOverflow?】
① 无法直接获取截断后的内容: textOverflow 主要用于处理文本过长时的显示问题,但不能直接获取到文本被截断后的内容。这使得在分页时难以准确判断当前页面显示的是文本的哪一部分。
② 难以实现分页逻辑: 由于 textOverflow 不提供获取截断文本内容的API,因此难以实现精确的分页逻辑,比如计算每页显示的内容范围。
③ 用户体验: 使用 Scroll 和 Text 的组合可以更好地控制文本的显示和分页,从而提供更平滑的阅读体验。
【总结】
通过上述步骤,构建了一个简单但功能完备的分页滚动文本组件,可用于展示长文本内容,适用于多种场景。用户可以方便地通过“上一页”和“下一页”按钮浏览不同页面,而无需担心内容的显示问题。