HarmonyOS 鸿蒙Next PageFlip翻书效果demo 鸿蒙场景化代码
阅读器翻页效果demo
介绍
本示例基于显式动画、List组件、drawing接口实现了阅读器上下翻页、左右覆盖翻页以及仿真翻页效果。
阅读器翻页效果源码地址
效果图
使用说明
进入应用默认为仿真翻页,长按屏幕并滑动可实现翻页效果,点击屏幕中部区域,弹出翻页方式选择栏。
选择上下翻页,显示上下翻页页面,支持上下滑动翻页。
选择覆盖翻页,显示覆盖翻页页面。支持左右滑动翻页,以及点击屏幕左右侧后滑动翻页。
实现思路
上下翻页效果
使用List组件实现上下滑动效果,通过添加点击事件,点击屏幕,底部会出现翻页方式选择栏。核心代码如下,源码参考
UpDownFlipPage.ets
Column() {
List({ initialIndex: this.currentPageNum - Constants.PAGE_FLIP_PAGE_COUNT }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Text($r(item))
.fontSize($r('app.integer.flip_page_text_font_size'))
.width($r('app.string.page_flip_full_size'))
.lineHeight($r('app.integer.flip_page_text_line_height'))
.padding({ left: $r('app.integer.flip_page_padding_middle_two') })
.fontColor($r('app.color.text_font_color'))
.fontWeight(FontWeight.Normal)
.letterSpacing(1)
}
}, (item: string, index: number) => index + JSON.stringify(item))
}
.width($r('app.string.pageflip_bottomview_row_text_width'))
.height($r('app.string.page_flip_full_size'))
.scrollBar(BarState.Off)
.cachedCount(Constants.PAGE_FLIP_CACHE_COUNT)
.onScrollIndex((firstIndex: number) => {
this.currentPageNum = firstIndex + Constants.PAGE_FLIP_PAGE_COUNT;
})
}
.width($r('app.string.page_flip_full_size'))
.backgroundColor($r("app.color.page_flip_background_color"))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.onClick((event?: ClickEvent) => {
if (event) {
if (this.isMenuViewVisible) {
this.isMenuViewVisible = false;
} else {
this.isMenuViewVisible = true;
}
}
})
覆盖翻页效果
使用显式动画animateTo和堆叠容器Stack实现页面覆盖效果,通过触发滑动手势事件PanGesture,左右滑动屏幕或点击屏幕左侧右侧可进行翻页。animateTo中涉及的参数:
duration:动画执行的时长,单位毫秒,项目中固定为300ms。
curve:插值曲线,此处用了EaseOut,表示动画逐渐减速以低速结束。
核心代码如下,源码参考
CoverFlipPage.ets
private clickAnimateTo(isClick: boolean, isLeft?: boolean) {
animateTo({
duration: Constants.PAGE_FLIP_TO_AST_DURATION,
curve: Curve.EaseOut,
onFinish: () => {
if (this.offsetX > Constants.PAGE_FLIP_RIGHT_FLIP_OFFSETX &&
this.currentPageNum !== Constants.PAGE_FLIP_PAGE_START) {
this.currentPageNum -= Constants.PAGE_FLIP_PAGE_COUNT;
} else if (this.offsetX < Constants.PAGE_FLIP_LEFT_FLIP_OFFSETX &&
this.currentPageNum !== Constants.PAGE_FLIP_PAGE_END) {
this.currentPageNum += Constants.PAGE_FLIP_PAGE_COUNT;
}
this.offsetX = Constants.PAGE_FLIP_ZERO;
this.simulatePageContent();
}
}, () => {
if (isClick) {
if (isLeft) {
this.offsetX = this.screenW;
} else {
this.offsetX = -this.screenW;
}
} else {
if (this.offsetX > Constants.PAGE_FLIP_RIGHT_FLIP_OFFSETX &&
this.currentPageNum !== Constants.PAGE_FLIP_PAGE_START) {
this.offsetX = this.screenW;
} else if (this.offsetX < Constants.PAGE_FLIP_LEFT_FLIP_OFFSETX &&
this.currentPageNum !== Constants.PAGE_FLIP_PAGE_END) {
this.offsetX = -this.screenW;
} else {
this.offsetX = Constants.PAGE_FLIP_ZERO;
}
}
});
}
仿真翻页效果
使用@ohos.graphics.drawing接口及NodeContainer组件,实现仿真翻页效果的绘制。根据手指滑动触摸位置,计算仿真页的边缘节点,填充区域后实现。核心代码如下,源码参考
EmulationFlipPage.ets
NodeContainer(this.myNodeController)
.onAppear(() => {
AppStorage.setOrCreate('colorFlag', true);
this.newRectNode();
})
.width(px2vp(this.windowWidth))
.height(px2vp(this.windowHeight))
.gesture(
PanGesture()
.onActionUpdate((event: GestureEvent) => {
AppStorage.setOrCreate('drawState', DrawState.DS_MOVING);
clearInterval(this.timeID);
for (let i = 0; i < event.fingerList.length; i++) {
if (event.fingerList[i] == undefined ||
event.fingerList[i].localX < 0 ||
event.fingerList[i].localY < 0 ||
event.fingerList[i].localX > px2vp(this.windowWidth) ||
event.fingerList[i].localY > px2vp(this.windowHeight)) {
return;
}
if (this.panPositionX < event.fingerList[i].localX) {
this.moveForward = MoveForward.MF_FORWARD;
this.panPositionX = event.fingerList[i].localX;
} else {
this.moveForward = MoveForward.MF_BACKWARD;
this.panPositionX = event.fingerList[i].localX;
}
this.positionX = vp2px(event.fingerList[i].localX);
this.positionY = vp2px(event.fingerList[i].localY);
AppStorage.setOrCreate('positionX', this.positionX);
AppStorage.setOrCreate('positionY', this.positionY);
if (!this.isDrawing) {
this.isDrawing = true;
if (this.positionY < (this.windowHeight / Constants.PAGE_FLIP_THREE)) {
AppStorage.setOrCreate('drawPosition', DrawPosition.DP_TOP);
} else if (this.positionY >
(this.windowHeight * Constants.PAGE_FLIP_TWO / Constants.PAGE_FLIP_THREE)) {
AppStorage.setOrCreate('drawPosition', DrawPosition.DP_BOTTOM);
} else {
AppStorage.setOrCreate('drawPosition', DrawPosition.DP_MIDDLE);
}
}
this.newRectNode();
}
})
.onActionEnd(() => {
AppStorage.set('drawState', DrawState.DS_RELEASE);
let num: number = Constants.DISTANCE_FRACTION;
if (this.moveForward === 1) {
let xDiff = (this.windowWidth - (AppStorage.get('positionX') as number)) / num;
let yDiff = 0;
if (AppStorage.get('drawPosition') as number === DrawPosition.DP_BOTTOM) {
yDiff = (this.windowHeight - (AppStorage.get('positionY') as number)) / num;
} else {
yDiff = (0 - (AppStorage.get('positionY') as number)) / num;
}
this.setTimer(xDiff, yDiff, () => {
this.newRectNode();
});
} else {
AppStorage.setOrCreate('positionY', (AppStorage.get('flipPositionY') as number));
this.setTimer(Constants.FLIP_X_DIFF, 0, () => {
this.newRectNode();
});
}
this.isDrawing = !this.isDrawing;
})
)
工程结构&模块类型
├──entry/src/main/ets/
│ ├──common
│ │ └──Constants.ets // 公共常量类
│ ├──entryability
│ │ └──EntryAbility.ets // 程序入口类
│ ├──page
│ │ └──Index.ets // 首页
│ ├──view
│ │ ├──BottomView.ets // 按钮弹窗
│ │ ├──CoverFlipPage.ets // 覆盖翻页
│ │ ├──EmulationFlipPage.ets // 仿真翻页
│ │ └──UpDownFlipPage.ets // 上下翻页
│ └──viewmodel
│ ├──BasicDataSource.ets // 列表数据类
│ └──PageNodeController.ets // 节点控制类
└──entry/src/main/resource // 应用静态资源目录
参考资料
无