HarmonyOS应用<奇妙科学乐园>开发第80篇:animateTo显式动画——弹窗/卡片/列表过渡效果

📖 引言
在上一篇文章中,我们深入探讨了 AnimationDemo 组件的 setInterval + Math.sin 数值驱动动画机制,它实现了太阳系、海洋世界、雨天气象等六种持续运行的场景动画。然而,科普应用中的动画不仅限于"一直循环播放"的场景演示——更多时候,我们需要的是由用户操作触发的过渡动画:点击卡片后展开详情、打卡弹窗弹出时的缩放入场、答题反馈区域的淡入淡出、收藏按钮的状态切换等等。这些动画的共同特点是:由离散事件触发,有明确的起点和终点,持续一段有限的时间后结束。
对于这类"事件驱动型"动画,ArkUI 提供了 animateTo 显式动画 API。它是 HarmonyOS 动画体系中最常用、最灵活的过渡动画方案。本文将以《奇妙科学乐园》项目中的实际场景为切入点,系统讲解 animateTo 的核心参数(curve 缓动曲线、duration 时长、iterations 重复次数、onFinish 回调),并结合弹窗入场、卡片展开、答题反馈、收藏切换等具体案例,演示如何用显式动画提升儿童科普应用的交互体验。
🎯 学习目标
完成本文后,你将能够:
- ✅ 理解
animateTo显式动画与setInterval数值动画的本质区别 - ✅ 掌握
animateTo的duration、curve、iterations、onFinish四大核心参数 - ✅ 运用
animateTo实现弹窗缩放入场 / 卡片展开收起 / 答题反馈过渡 - ✅ 理解
Curve.EaseInOut、Curve.Friction等常见缓动曲线的适用场景 - ✅ 掌握
iterations重复动画的设计技巧(如按钮闪烁提示) - ✅ 学会通过
onFinish回调实现动画串联和状态同步
💡 需求分析
animateTo vs setInterval:两种动画范式的对比
在《奇妙科学乐园》项目中,我们实际使用了两种不同的动画技术路线:
| 维度 | setInterval + Math.sin(第47篇) | animateTo 显式动画(本篇) |
|---|---|---|
| 触发方式 | 自动持续运行 | 用户事件触发 |
| 动画时长 | 无限循环 | 有限时长(毫秒级) |
| 状态变化 | 连续数值驱动(0-99循环) | 离散状态切换(boolean等) |
| 适用场景 | 场景演示(太阳系、鲸鱼) | UI过渡(弹窗、展开、反馈) |
| 性能特点 | 每帧重新计算 | 由渲染引擎优化插值 |
| 代码复杂度 | 需手动管理定时器生命周期 | 一行API调用即可 |
项目中需要 animateTo 的场景清单
奇妙科学乐园 - 需要显式动画的交互场景
├── 打卡弹窗 CheckInDialog
│ └── 弹窗弹出时的缩放入场效果
├── 答题页面 Quiz
│ ├── 选择答案后反馈区域的淡入过渡
│ ├── 答题进度条的平滑增长
│ └── 下一题时的内容切换动画
├── 文章详情页 TopicDetail
│ ├── 收藏按钮的状态切换动画
│ └── 底部操作栏的弹出/收起
├── 答题结果页 QuizResult
│ └── 星星评级的逐个点亮动画
├── 实验详情页 LabDetail
│ └── 步骤切换时的内容过渡
└── 首页 Index
└── 骨架屏到真实内容的切换过渡
animateTo API 签名解析
// animateTo 完整签名
animateTo(
options: AnimationOptions, // 动画配置参数
event: () => void // 状态变更闭包
): void
// AnimationOptions 核心字段
interface AnimationOptions {
duration?: number; // 动画时长(毫秒),默认1000
curve?: Curve | string; // 缓动曲线,默认Curve.EaseInOut
delay?: number; // 延迟执行(毫秒),默认0
iterations?: number; // 重复次数,默认1(-1为无限循环)
playMode?: PlayMode; // 播放模式(Normal/Reverse/Alternate)
onFinish?: () => void; // 动画结束回调
}
🏗️ 整体架构设计
显式动画的工作原理
animateTo 的核心原理可以用一句话概括:在闭包中修改 @State 变量时,ArkUI 渲染引擎不会立即跳到终态,而是按照指定的 duration 和 curve 在起始值和终止值之间进行插值,逐帧更新 UI。
用户点击 → animateTo 启动 → 闭包中修改 @State → 引擎开始插值动画
时间轴:
0ms 150ms 300ms 450ms 600ms(duration结束)
|-----------|-----------|-----------|-----------|
起始值 ----→ 25% ----→ 50% ----→ 75% ----→ 终止值
↑
curve控制每帧的插值比例
缓动曲线 curve 对动画质感的影响
缓动曲线决定了动画过程中"速度"的变化方式,是动画"手感"的关键参数:
| Curve 值 | 速度变化 | 适用场景 | 直观感受 |
|---|---|---|---|
Curve.Linear |
匀速 | 进度条、计时器 | 机械、无感情 |
Curve.EaseIn |
慢→快 | 元素离场 | 加速离开 |
Curve.EaseOut |
快→慢 | 元素入场 | 快速到达后减速 |
Curve.EaseInOut |
慢→快→慢 | 状态切换 | 最自然的过渡 |
Curve.Friction |
快→缓慢停止 | 物理模拟 | 弹性惯性 |
Curve.Sharp |
极快→缓慢 | 点击反馈 | 敏捷响应 |
Curve.Cubic |
三次贝塞尔 | 通用过渡 | 平滑柔和 |
spring() |
弹簧物理 | 按钮回弹 | 自然弹跳(第82篇详解) |
🔧 核心实现拆解
1. 打卡弹窗的缩放入场动画
CheckInDialog 是《奇妙科学乐园》的每日签到弹窗,目前使用 CustomDialogController 的标准弹出效果。我们可以用 animateTo 为弹窗内的元素添加更丰富的入场动画,让签到体验更具仪式感。
当前实现回顾(CheckInDialog.ets):
// entry/src/main/ets/components/common/CheckInDialog.ets
@CustomDialog
export struct CheckInDialog {
controller: CustomDialogController;
@State streakDays: number = 0;
@State weekDays: string[] = ['一', '二', '三', '四', '五', '六', '日'];
@State checkedDays: boolean[] = [false, false, false, false, false, false, false];
@State todayChecked: boolean = false;
// ... 省略其他逻辑
}
使用 animateTo 增强打卡按钮的点击反馈:
// 打卡按钮点击时,使用 animateTo 添加缩放动画
onCheckIn(): void {
if (this.todayChecked) return;
// ✅ 正确:使用 animateTo 包裹状态变更,实现平滑过渡
animateTo({
duration: 300,
curve: Curve.EaseInOut,
onFinish: () => {
// 动画完成后延迟关闭弹窗
setTimeout(() => {
this.controller.close();
}, 500);
}
}, () => {
// 闭包内修改 @State 变量,触发动画插值
this.checkedDays[this.getCurrentDayIndex()] = true;
this.todayChecked = true;
});
// 持久化打卡记录
userPrefs.checkInAndUpdateDays().then((days: number) => {
// ✅ 正确:在 animateTo 之外修改非动画状态
animateTo({ duration: 400, curve: Curve.EaseOut }, () => {
this.streakDays = days;
});
});
}
关键设计要点:
duration: 300:300毫秒是按钮状态切换的"黄金时长",既不过快让用户看不清,也不过慢造成等待感。Curve.EaseInOut:慢→快→慢的节奏让状态切换显得自然。onFinish回调:在动画播放完毕后才关闭弹窗,确保用户看到完整的打卡确认效果。- 连续两天数的
animateTo与打卡状态的animateTo分离,避免互相干扰。
对比:不使用 animateTo 的问题:
// ❌ 错误:直接修改状态,无过渡效果
onCheckIn(): void {
if (this.todayChecked) return;
this.checkedDays[this.getCurrentDayIndex()] = true;
this.todayChecked = true;
// UI瞬间跳变,体验生硬
setTimeout(() => {
this.controller.close();
}, 800);
}
2. 答题反馈区域的淡入过渡
在 Quiz 答题页面中,用户选择答案后会出现绿色(正确)或红色(错误)的反馈区域。当前实现是直接显示,没有任何过渡效果。我们用 animateTo 为反馈区域添加淡入 + 位移的入场动画。
当前实现回顾(Quiz.ets 中 showFeedback 的条件渲染):
// 当前实现:直接显示反馈区域
if (this.showFeedback) {
Column({ space: 8 }) {
Row() {
Image(this.isCorrect ? trophy icon : empty icon);
Text(this.isCorrect ? '回答正确!' : '回答错误');
}
// ... 解释内容
}
.width('100%')
.padding(14)
.backgroundColor(this.isCorrect ? '#e8f5e9' : '#ffebee');
}
使用 animateTo + @State 实现反馈区域动画:
@State showFeedback: boolean = false;
@State feedbackOpacity: number = 0; // 控制透明度
@State feedbackOffsetY: number = 20; // 控制Y轴偏移(从下方滑入)
@State feedbackScale: number = 0.95; // 控制缩放
selectOption(index: number) {
if (this.showFeedback) return;
this.selectedOption = index;
const result = quizEngine.submitAnswer(index);
if (result) {
this.isCorrect = result.isCorrect;
this.correctIdx = result.correctIndex;
this.explanation = result.explanation;
// ✅ 正确:用 animateTo 驱动反馈区域的入场动画
animateTo({
duration: 350,
curve: Curve.EaseOut,
delay: 200, // 延迟200ms,让用户先看到选中状态
}, () => {
this.showFeedback = true;
this.feedbackOpacity = 1;
this.feedbackOffsetY = 0;
this.feedbackScale = 1;
});
}
}
在 build 中使用动画状态:
if (this.showFeedback) {
Column({ space: 8 }) {
// ... 反馈内容
}
.width('100%')
.padding(14)
.backgroundColor(this.isCorrect ? '#e8f5e9' : '#ffebee')
.borderRadius(12)
.border({ width: 1, color: this.isCorrect ? '#a5d6a7' : '#ef9a9a' })
.opacity(this.feedbackOpacity) // 透明度动画
.translate({ y: this.feedbackOffsetY }) // 位移动画
.scale({ x: this.feedbackScale, y: this.feedbackScale }) // 缩放动画
.margin({ bottom: 16 });
}
✅ 正确做法:使用多个 @State 变量(opacity、offsetY、scale)分别控制不同的动画属性,animateTo 会同时对这些属性进行插值。
❌ 错误做法:只用 showFeedback 的 true/false 控制显示/隐藏,没有过渡效果,体验生硬。
答题进度条的平滑增长:
// 当前实现:进度条直接跳变
Progress({ value: this.currentIndex + 1, total: this.questionCount })
// ✅ 优化:用 animateTo 实现进度条平滑过渡
@State progressValue: number = 1;
nextQuestion() {
const hasMore = quizEngine.nextQuestion();
if (hasMore) {
const nextQ = quizEngine.getCurrentQuestion();
this.currentIndex++;
this.selectedOption = -1;
this.showFeedback = false;
// 进度条平滑增长
animateTo({ duration: 400, curve: Curve.EaseInOut }, () => {
this.progressValue = this.currentIndex + 1;
});
// 反馈区域重置为隐藏状态(不使用动画,瞬间重置)
this.feedbackOpacity = 0;
this.feedbackOffsetY = 20;
this.feedbackScale = 0.95;
this.currentQuestion = nextQ;
} else {
this.currentQuestion = null;
this.showFeedback = false;
}
}
3. 收藏按钮的状态切换动画
TopicDetail 页面底部的收藏按钮,当前实现是通过 promptAction.showToast 显示"已收藏/已取消收藏"的提示。我们可以用 animateTo 让按钮本身产生视觉反馈,增强操作确认感。
当前实现回顾:
toggleFavorite() {
if (!this.topic) return;
userPrefs.toggleFavorite(this.topic.id).then((isFav: boolean) => {
this.isFavorite = isFav;
promptAction.showToast({
message: isFav ? '已收藏' : '已取消收藏',
duration: 1500
});
});
}
使用 animateTo 增强收藏按钮反馈:
@State isFavorite: boolean = false;
@State collectIconScale: number = 1; // 收藏图标缩放
@State collectBgColor: string = ThemeColors.BG_PRIMARY; // 按钮背景色
toggleFavorite() {
if (!this.topic) return;
userPrefs.toggleFavorite(this.topic.id).then((isFav: boolean) => {
// ✅ 正确:用 animateTo 实现收藏状态的平滑切换
animateTo({
duration: 250,
curve: Curve.EaseOut,
iterations: 1, // 只播放一次
onFinish: () => {
// 动画结束后恢复图标原始大小
animateTo({ duration: 200, curve: Curve.EaseInOut }, () => {
this.collectIconScale = 1;
});
}
}, () => {
this.isFavorite = isFav;
this.collectBgColor = isFav ? '#fff0f0' : ThemeColors.BG_PRIMARY;
this.collectIconScale = 1.3; // 先放大
});
promptAction.showToast({
message: isFav ? '已收藏' : '已取消收藏',
duration: 1500
});
}).catch((err: Error) => {
Logger.error(TAG, '切换收藏失败', err);
promptAction.showToast({
message: '操作失败,请重试',
duration: 1500
});
});
}
在 build 中应用动画状态:
Row({ space: 6 }) {
Image($r('app.media.icon_collect'))
.width(16)
.height(16)
.fillColor(this.isFavorite ? ThemeColors.PRIMARY : '#cccccc')
.scale({ x: this.collectIconScale, y: this.collectIconScale }); // 缩放动画
Text('收藏')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor(ThemeColors.PRIMARY);
}
.padding({ left: 24, right: 24, top: 10, bottom: 10 })
.backgroundColor(this.collectBgColor) // 背景色动画
.border({ width: 1, color: ThemeColors.PRIMARY })
.borderRadius(9999)
.onClick(() => {
this.toggleFavorite();
});
动画时序分析:
用户点击收藏
│
├─ 0ms: animateTo 启动
│ ├─ isFavorite = true
│ ├─ collectBgColor = '#fff0f0'
│ └─ collectIconScale = 1.3(图标放大)
│
├─ 250ms: onFinish 回调触发
│ └─ animateTo 启动(恢复动画)
│ └─ collectIconScale = 1(图标恢复)
│
└─ 450ms: 全部动画完成
└─ 图标先放大再缩回,形成"弹跳"效果
4. iterations 重复动画:按钮闪烁提示
animateTo 的 iterations 参数可以实现动画的重复播放。在《奇妙科学乐园》中,我们可以用它来实现"签到提醒按钮"的闪烁效果,引导儿童用户注意到需要打卡。
@State checkInBtnOpacity: number = 1;
private blinkTimer: number = -1;
aboutToAppear() {
// 签到按钮闪烁提示:持续闪烁直到用户点击
this.startCheckInBlink();
}
aboutToDisappear() {
if (this.blinkTimer !== -1) {
clearInterval(this.blinkTimer);
this.blinkTimer = -1;
}
}
/**
* 签到按钮闪烁提示动画
* 使用 animateTo + iterations 实现有限次重复
*/
startCheckInBlink(): void {
if (this.todayChecked) return;
// ✅ 正确:iterations: 3 表示动画重复3次
animateTo({
duration: 600,
curve: Curve.EaseInOut,
iterations: 3, // 闪烁3次后停止
playMode: PlayMode.Alternate, // 正反交替播放
onFinish: () => {
// 3次闪烁后,每5秒再次闪烁,持续提醒
if (!this.todayChecked) {
this.blinkTimer = setTimeout(() => {
this.startCheckInBlink();
}, 5000);
}
}
}, () => {
this.checkInBtnOpacity = 0.5; // 从1变到0.5
});
}
onCheckIn(): void {
if (this.todayChecked) return;
// 停止闪烁
if (this.blinkTimer !== -1) {
clearTimeout(this.blinkTimer);
this.blinkTimer = -1;
}
// 立即恢复按钮状态
this.checkInBtnOpacity = 1;
// ... 其他打卡逻辑
}
iterations 参数详解:
| iterations 值 | 行为 | 适用场景 |
|---|---|---|
1(默认) |
播放一次 | 普通状态切换 |
2 |
播放两次 | 双击强调 |
3 |
播放三次 | 闪烁提示 |
-1 |
无限循环 | 加载指示器 |
playMode 参数详解:
| PlayMode 值 | 行为 | 每次迭代效果 |
|---|---|---|
PlayMode.Normal |
每次从头播放 | A→B, A→B, A→B |
PlayMode.Reverse |
每次反向播放 | A→B, B→A, A→B |
PlayMode.Alternate |
正反交替 | A→B, B→A, A→B |
✅ 正确做法:PlayMode.Alternate + iterations: 3 组合,实现"亮→暗→亮→暗→亮→暗"的闪烁效果。
❌ 错误做法:用 setInterval + setTimeout 手动控制闪烁,代码复杂且容易出 bug。
5. 首页骨架屏到真实内容的切换过渡
Index 页面从骨架屏切换到真实内容时,当前是瞬间跳变。用 animateTo 可以实现骨架屏的淡出 + 真实内容的淡入,过渡更加自然。
@State isLoading: boolean = true;
@State contentOpacity: number = 0; // 内容区域透明度
@State skeletonOpacity: number = 1; // 骨架屏透明度
/**
* 从 scienceData 加载数据
*/
private loadData(): void {
this.hasError = false;
// ✅ 正确:用 animateTo 实现骨架屏→内容的平滑过渡
animateTo({
duration: 500,
curve: Curve.EaseInOut,
}, () => {
this.isLoading = false;
this.contentOpacity = 1;
this.skeletonOpacity = 0;
});
if (this.loadTimer >= 0) {
clearInterval(this.loadTimer);
this.loadTimer = -1;
}
this.recommendedTopics = scienceData.getRecommendedTopics(3);
this.categories = scienceData.getAllCategories();
}
在 build 中应用透明度:
// 骨架屏:加载中
if (this.isLoading) {
Column() {
this.SkeletonContent();
}
.width('100%')
.height('100%')
.opacity(this.skeletonOpacity); // 淡出
} else if (this.hasError) {
this.ErrorContent();
} else {
// 正常状态:淡入
Column() {
// ... 真实内容
}
.width('100%')
.height('100%')
.opacity(this.contentOpacity); // 淡入
}
6. 实验步骤切换的过渡动画
LabDetail 页面在切换实验步骤时,当前是直接替换内容。我们可以添加 animateTo 实现步骤内容的淡出→淡入过渡。
@State currentStep: number = 0;
@State stepContentOpacity: number = 1; // 步骤内容透明度
@State stepContentOffsetY: number = 0; // 步骤内容Y偏移
nextStep() {
if (this.experiment && this.currentStep < this.experiment.steps.length - 1) {
// ✅ 正确:先淡出当前步骤
animateTo({
duration: 200,
curve: Curve.EaseIn,
onFinish: () => {
// 淡出完成后切换步骤
this.currentStep++;
this.stepContentOffsetY = 20; // 设置偏移初始值
// 再淡入新步骤
animateTo({
duration: 300,
curve: Curve.EaseOut,
}, () => {
this.stepContentOpacity = 1;
this.stepContentOffsetY = 0;
});
}
}, () => {
this.stepContentOpacity = 0;
this.stepContentOffsetY = -20; // 向上滑出
});
}
}
动画时序分析:
用户点击"下一步"
│
├─ 0ms: animateTo(淡出) 启动
│ ├─ stepContentOpacity: 1 → 0
│ └─ stepContentOffsetY: 0 → -20(向上滑出)
│
├─ 200ms: onFinish 触发
│ ├─ currentStep++(切换步骤数据)
│ └─ animateTo(淡入) 启动
│ ├─ stepContentOpacity: 0 → 1
│ └─ stepContentOffsetY: 20 → 0(从下方滑入)
│
└─ 500ms: 全部动画完成
✅ 正确做法:通过 onFinish 回调实现"先淡出→切换数据→再淡入"的动画串联。
❌ 错误做法:同时修改 currentStep 和透明度,导致"新内容以半透明状态出现"的闪烁问题。
📊 animateTo 参数速查表
duration 时长选择指南
| 时长范围 | 感受 | 适用场景 | 项目案例 |
|---|---|---|---|
| 100-200ms | 快速、敏捷 | 按钮按压、图标切换 | 收藏图标弹跳 |
| 200-400ms | 自然、舒适 | 状态切换、内容展开 | 答题反馈淡入 |
| 400-600ms | 从容、优雅 | 页面过渡、大区块变化 | 骨架屏切换 |
| 600-1000ms | 缓慢、隆重 | 强调性动画 | 步骤切换 |
curve 曲线选择指南
// 根据交互类型选择合适的缓动曲线
// ✅ 入场动画:快速出现,缓慢稳定
animateTo({ curve: Curve.EaseOut, duration: 300 }, () => { ... });
// ✅ 退场动画:缓慢启动,快速离开
animateTo({ curve: Curve.EaseIn, duration: 200 }, () => { ... });
// ✅ 状态切换:两端缓慢,中间快速
animateTo({ curve: Curve.EaseInOut, duration: 400 }, () => { ... });
// ✅ 物理交互:模拟惯性减速
animateTo({ curve: Curve.Friction, duration: 500 }, () => { ... });
// ✅ 快捷操作:极速响应
animateTo({ curve: Curve.Sharp, duration: 150 }, () => { ... });
iterations 使用模板
// ✅ 单次播放(默认)
animateTo({ iterations: 1 }, () => { this.scale = 1.2; });
// ✅ 有限次重复(如闪烁3次)
animateTo({ iterations: 3, playMode: PlayMode.Alternate }, () => {
this.opacity = 0.5;
});
// ✅ 无限循环(如加载动画)
animateTo({ iterations: -1, playMode: PlayMode.Alternate }, () => {
this.rotation = 360;
});
⚠️ 避坑指南
1. 闭包外修改状态不会触发动画
// ❌ 错误:在 animateTo 闭包之外修改状态,不产生动画效果
animateTo({ duration: 300, curve: Curve.EaseInOut }, () => {
this.showFeedback = true;
});
this.feedbackOpacity = 1; // 这行在闭包外,不会参与动画插值!
// ✅ 正确:所有需要动画过渡的状态变更都必须放在闭包内
animateTo({ duration: 300, curve: Curve.EaseInOut }, () => {
this.showFeedback = true;
this.feedbackOpacity = 1;
this.feedbackOffsetY = 0;
});
2. 异步回调中使用 animateTo 的注意事项
// ❌ 错误:在 Promise 回调中直接使用 animateTo 可能不生效
userPrefs.toggleFavorite(id).then((isFav) => {
// 如果此时组件已被销毁,animateTo 可能报错
animateTo({ duration: 300 }, () => { this.isFavorite = isFav; });
});
// ✅ 正确:添加组件存活检查
userPrefs.toggleFavorite(id).then((isFav: boolean) => {
if (!this.topic) return; // 组件已销毁,不执行动画
animateTo({ duration: 300, curve: Curve.EaseOut }, () => {
this.isFavorite = isFav;
});
});
3. duration 不宜过长
// ❌ 错误:duration 设置过长,用户等待感明显
animateTo({ duration: 2000 }, () => { this.showDetail = true; });
// ✅ 正确:儿童应用中,单个动画不超过600ms
animateTo({ duration: 400, curve: Curve.EaseInOut }, () => {
this.showDetail = true;
});
4. 多个 animateTo 嵌套的风险
// ❌ 错误:animateTo 嵌套调用,可能导致动画状态混乱
animateTo({ duration: 300 }, () => {
this.opacity = 0.5;
animateTo({ duration: 200 }, () => { // 嵌套!
this.scale = 1.2;
});
});
// ✅ 正确:使用 onFinish 串联动画
animateTo({
duration: 300,
curve: Curve.EaseOut,
onFinish: () => {
animateTo({ duration: 200, curve: Curve.EaseInOut }, () => {
this.scale = 1.2;
});
}
}, () => {
this.opacity = 0.5;
});
5. 动画属性必须是可插值的
// ❌ 错误:boolean 类型不能被 animateTo 插值
animateTo({ duration: 300 }, () => {
this.showFeedback = true; // boolean 跳变,无插值过程
});
// ✅ 正确:使用数值类型控制可插值属性
animateTo({ duration: 300 }, () => {
this.showFeedback = true; // 控制 if 渲染
this.feedbackOpacity = 1; // opacity 可插值
this.feedbackOffsetY = 0; // translateY 可插值
});
🔄 进阶思考:animateTo 与 transition 的对比
ArkUI 还提供了 transition 属性式转场动画,它与 animateTo 的区别在于:
| 维度 | animateTo 显式动画 | transition 属性式动画 |
|---|---|---|
| 触发方式 | 代码主动调用 | 组件出现/消失时自动触发 |
| 控制粒度 | 精确控制时长、曲线 | 相对固定 |
| 适用场景 | 状态变更的过渡 | if/ForEach 控制的组件出入场 |
| 配合使用 | 独立使用 | 可与 animateTo 叠加 |
// transition 的用法(组件出入场自动动画)
if (this.showFeedback) {
Column() {
Text('回答正确!')
}
.transition(TransitionEffect.opacity(AnimationConstants.DURATION)
.translate({ y: 20 }))
// 当 showFeedback 从 false 变为 true 时,
// 组件自动执行 opacity 0→1 + translateY 20→0 的入场动画
}
在《奇妙科学乐园》中,我们推荐:
- 状态数值变化(opacity、scale、translate):使用
animateTo - 组件出现/消失(if 条件渲染):使用
transition或animateTo+ 数值控制 - 页面间跳转:使用
PageTransitionEnter/Exit(第81篇详解)
📝 小结
animateTo 显式动画是 ArkUI 动画体系中使用频率最高的 API。本文从以下七个方面进行了系统讲解:
- 两种动画范式对比:setInterval 持续动画 vs animateTo 事件驱动动画的本质区别
- API 签名解析:duration、curve、delay、iterations、playMode、onFinish 六大参数详解
- 打卡弹窗动画:onFinish 回调实现动画完成后的后续操作
- 答题反馈过渡:opacity + translateY + scale 多属性组合动画
- 收藏按钮反馈:onFinish 串联实现"放大→恢复"的弹跳效果
- iterations 闪烁提示:PlayMode.Alternate 实现签到按钮的有限次闪烁
- 步骤切换动画:onFinish 串联"淡出→切换→淡入"的复合动画
核心设计理念:显式动画的本质是"闭包内修改 @State,引擎负责插值"。开发者只需关注"从什么状态变到什么状态",以及"花多长时间、按什么节奏变化",具体的逐帧计算由 ArkUI 渲染引擎自动完成。 这种声明式的动画思维,让复杂过渡效果的实现变得极其简洁。
源码仓库:https://atomgit.com/2301_79280419/WonderSciencePark
相关文件:
entry/src/main/ets/components/common/CheckInDialog.etsentry/src/main/ets/pages/Quiz.etsentry/src/main/ets/pages/TopicDetail.etsentry/src/main/ets/pages/LabDetail.etsentry/src/main/ets/pages/Index.ets
🔗 相关链接
更多推荐



所有评论(0)