HarmonyOS 5鸿蒙场景技术共建能力:实现阅读器翻页效果
一、前言与背景:
大家好,我是完美句号!欢迎来到 HarmonyOS 5 开发实战系列。本系列致力于为开发者提供实用的技术方案和即拿即用的代码示例,帮助大家快速掌握 HarmonyOS Next 应用开发中的核心功能。在HarmonyOS 5鸿蒙系统中,基于显式动画、List组件、drawing接口实现了阅读器上下翻页、左右覆盖翻页、仿真翻页等效果。
二、阅读器翻页效果:
阅读器翻页效果是阅读器应用中常见的翻页效果,在阅读器应用中,翻页时可以使用不同的效果展示页面变更,使用List组件作为容器组件,提供上下滑动的能力。使用ListItem组件存放每一页的内容。页面内容可以自行定义,本文使用Text组件展示文本内容。通常有以下翻页效果:
- 进入应用默认为仿真翻页,在屏幕上滑动手指执行翻页。支持点击左右两侧自动翻页。点击屏幕中部区域,弹出翻页选项。
- 选择上下翻页,显示上下翻页页面。支持上下滑动。
- 选择覆盖翻页,显示覆盖翻页页面。支持左右滑动翻页,以及点击屏幕左右侧后滑动翻页。
工程目录:
├──entry/src/main/ets/
│ ├──common
│ │ └──Constants.ets // 公共常量类
│ ├──entryability
│ │ └──EntryAbility.ets // 程序入口类
│ ├──pages
│ │ └──Index.ets // 首页
│ ├──view
│ │ ├──BottomView.ets // 按钮弹窗
│ │ ├──CoverFlipPage.ets // 覆盖翻页
│ │ ├──EmulationFlipPage.ets // 仿真翻页
│ │ ├──ReaderPage.ets // 文字页面
│ │ └──UpDownFlipPage.ets // 上下翻页
│ └──viewmodel
│ ├──BasicDataSource.ets // 列表数据类
│ └──PageNodeController.ets // 节点控制类
└──entry/src/main/resource // 应用静态资源目录
实现原理:
-
使用List组件实现上下滑动效果;使用组件位移及显式动效animateTo实现左右覆盖翻页效果。
-
使用ArkGraphics 2D(方舟2D图形服务) @ohos.graphics.drawing接口及NodeContainer组件,实现仿真翻页效果的绘制。根据手指滑动触摸位置,计算仿真页的边缘节点,填充区域后实现。

在文本阅读器应用上,翻页时可以使用不同的效果展示页面变更,通常有以下翻页效果:
- 上下翻页:向上或向下的滑动效果,适合垂直滚动的文本阅读(如电子书、长篇文章)。
- 覆盖翻页:通过水平滑动使当前页面向左侧滑出显示下一页,上一页从左侧滑入覆盖当前页,形成连贯的过渡效果。
- 仿真翻页:模拟真实纸张的弯曲、翻折动作,例如页面边缘的弧形变形与阴影投影,实现沉浸式的体验效果。
- 本文主要对上述翻页效果的实现进行讲解,旨在帮助开发者了解常见翻页动效开发的流程及实现细节。
上下翻页时,页面内容沿着垂直方向移动。当用户向上滑动时,当前页面内容向上滑出屏幕顶部,同时下一页内容从屏幕底部滑入;向下滑动则相反(当前页向下滑出,上一页从顶部滑入)。实现效果如下:
三、实现原理:
开发步骤,构建模拟数据。
// entry/src/main/ets/view/UpDownFlipPage.ets
@Link currentPageNum: number;
private data: BasicDataSource = new BasicDataSource([]);
// ...
aboutToAppear(): void {
for (let i = Constants.PAGE_FLIP_PAGE_START; i <= Constants.PAGE_FLIP_PAGE_END; i++) {
this.data.pushItem(Constants.PAGE_FLIP_RESOURCE + i.toString());
}
// ...
}
UpDownFlipPage.ets
使用List组件实现上下翻页效果。
// entry/src/main/ets/view/UpDownFlipPage.ets
List({ initialIndex: this.currentPageNum - 1, scroller: this.scroller }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Text($r(item))
// ...
}
}, (item: string, index: number) => index + JSON.stringify(item))
}
// ...
.onScrollIndex((firstIndex: number) => {
this.currentPageNum = firstIndex + 1;
})
覆盖翻页效果模拟卡片切换,新页面(上一页)从屏幕的左侧水平滑入,完全覆盖当前页面。当前页支持从屏幕另一侧滑出,滑出时显示下层新页面。在整个过程中页面没有弯曲或折叠效果,页面作为一个整体平面进行移动。效果如下:

使用Stack堆叠容器,存放1、2、3三个页面,借助图形变换的translate平移属性,将上层页面向左平移屏幕宽度移至窗口左侧。使用PanGesture滑动手势事件判断手势滑动方向及平移距离,依据滑动方向及平移距离,执行1页面向右平移滑入屏幕,或2页面向左平移滑出屏幕。滑动手势结束后通过显式动画 (animateTo)完成页面平移至窗口边缘,并重新渲染1、2、3页面。
使用Stack堆叠容器存放3个页面,并将上层页面向左平移至屏幕外。

// entry/src/main/ets/view/CoverFlipPage.ets
@Component
export struct CoverFlipPage {
// ...
@State offsetX: number = 0;
@State screenW: number = 0;
// ...
build() {
Stack() {
// Next page.
ReaderPage({ content: this.rightPageContent })
// Current page.
ReaderPage({ content: this.midPageContent })
.translate({ x: this.offsetX >= Constants.PAGE_FLIP_ZERO ? Constants.PAGE_FLIP_ZERO : this.offsetX })
// ...
// Previous page, shift the window width to the left.
ReaderPage({ content: this.leftPageContent })
.translate({ x: -this.screenW + this.offsetX })
// ...
}
// ...
}
// ...
}
根据滑动手势事件获取平移的距离,修改状态变量offsetX刷新页面,控制页面移动。手势结束后调用自定义方法pageAnimateTo()方法执行显示动画,完成页面剩余滑动。
// entry/src/main/ets/view/CoverFlipPage.ets
.gesture(
PanGesture(this.panOption)
.onActionUpdate((event?: GestureEvent) => {
// ...
this.offsetX = event.offsetX;
// ...
})
.onActionEnd(() => {
// ...
this.pageAnimateTo(false);
// ...
})
)
pageAnimateTo()方法中设置offSetX的结束值,手势向右时offsetX的值为屏幕宽度screenW,手势向左时offsetX的值为负的屏幕宽度-screenW。设置完成后animateTo方法自动插入过渡动画。动画播放结束后,onFinish()完成回调方法中重置offsetX为0,执行自定义方法simulatePageContent()方法更新各内容页ReaderPage组件展示的数据。

// entry/src/main/ets/view/CoverFlipPage.ets
private pageAnimateTo(isClick: boolean, isLeft?: boolean) {
this.getUIContext().animateTo({
duration: Constants.PAGE_FLIP_TO_AST_DURATION,
curve: Curve.EaseOut,
onFinish: () => {
// ...
this.offsetX = Constants.PAGE_FLIP_ZERO;
this.simulatePageContent();
// ...
}
}, () => {
// ...
this.offsetX = this.screenW;
// ...
this.offsetX = -this.screenW;
// ...
});
}
更多推荐



所有评论(0)