鸿蒙Next翻页动画实现:基于ArkUI的3D卡片翻转效果
效果概述
翻页效果是阅读类、日历类应用的核心交互之一。本文基于HarmonyOS Next的ArkUI框架,利用animateTo显式动画接口,实现逼真的书籍翻页视觉效果。通过双层堆叠布局与动态旋转控制,模拟真实翻页的物理过程。
核心机制解析
整个翻页系统由四个页面单元协同工作,采用Stack双层架构:
- 底层Row:包含静态占位页(C)与预加载页(D)
- 顶层Row:包含当前可见页(A)与动画执行页(B)
翻页时,B页携带内容旋转180°覆盖A页,同时D页提前准备下一页内容。动画完成后状态交换,B页复位至右侧等待下一次翻转。
页面组件封装
定义可复用的页面单元组件,通过@Prop接收动态参数控制旋转行为:
@Component
struct FlipPage {
@Prop index: number; // 页面序号
@Prop rotation: number; // 旋转角度值
@Prop pivotX: string; // 水平旋转轴心
@Prop pivotY: string; // 垂直旋转轴心
build() {
Text(`${this.index}`)
.fontSize(48)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Center)
.backgroundColor('#2E5AAC')
.width('45%')
.height('80%')
.borderRadius(12)
.rotate({
x: 0,
y: 1, // Y轴为旋转轴,实现左右翻页
z: 0,
angle: this.rotation,
centerX: this.pivotX,
centerY: this.pivotY
})
}
}
主框架搭建
使用Stack实现层级管理,配合Divider绘制书脊分隔线:
Stack({ alignContent: Alignment.Center }) {
// 底层:预渲染区域
Row() {
FlipPage({ // C页:固定占位
index: this.staticIndex,
rotation: 0,
pivotX: '100%',
pivotY: '50%'
})
FlipPage({ // D页:下一页内容预载
index: this.preloadIndex,
rotation: 0,
pivotX: '100%',
pivotY: '50%'
})
}
// 顶层:动画执行区域
Row() {
FlipPage({ // A页:当前显示(动画结束后更新)
index: this.displayIndex,
rotation: this.mirrorAngle, // 180°镜像翻转
pivotX: '50%',
pivotY: '50%'
})
FlipPage({ // B页:执行翻页动画
index: this.animIndex,
rotation: this.flipAngle,
pivotX: '100%',
pivotY: '50%'
})
}
// 书脊分隔线
Divider()
.strokeWidth(4)
.color(Color.White)
.height('85%')
.vertical(true)
}
.width('100%')
.height('100%')
.backgroundColor('#1A1A1A')
动画逻辑实现
通过定时器驱动动画循环,animateTo控制700ms的翻页过程:
private readonly TOTAL_PAGES = 8;
private flipAngle: number = 0; // B页旋转角度
private mirrorAngle: number = 0; // A页镜像角度
private displayIndex: number = 0; // A页显示序号
private animIndex: number = 1; // B页动画序号
private preloadIndex: number = 2; // D页预载序号
aboutToAppear(): void {
// 每秒触发一次翻页,周期需大于动画时长
setInterval(() => this.executeFlip(), 1000);
}
private executeFlip(): void {
animateTo(
{
duration: 700,
curve: Curve.EaseInOut,
onFinish: () => {
// 动画完成:状态转移
this.displayIndex = this.animIndex; // A页继承B页内容
this.mirrorAngle = 180; // A页镜像显示
this.flipAngle = 0; // B页角度复位
this.animIndex = (this.animIndex + 1) % this.TOTAL_PAGES; // B页更新序号
}
},
() => {
// 动画开始:触发旋转
this.flipAngle = 180; // B页开始翻转
this.preloadIndex = (this.animIndex + 1) % this.TOTAL_PAGES; // D页预载
}
);
}
状态流转图解
| 阶段 | A页(左) | B页(右) | C页(左底) | D页(右底) |
|---|---|---|---|---|
| 初始态 | 显示内容N,0° | 显示内容N+1,0° | 内容N,固定 | 内容N+2,固定 |
| 动画中 | 保持 | 0°→180°旋转 | 保持 | 保持 |
| 结束态 | 内容变为N+1,180°镜像 | 角度复位0°,内容N+2 | 保持 | 内容预载N+3 |
工程结构
flipanimation/
├── src/main/ets/
│ ├── components/
│ │ └── FlipPage.ets # 页面单元组件
│ └── pages/
│ └── BookFlipPage.ets # 翻页动画主页面
└── resources/
└── base/element/ # 样式资源定义