动画实战案例¶
本章通过分析项目中的实际代码,展示动画系统在真实场景中的应用。
HelloStyleDemo Dashboard 页面动效¶
源文件:example/HelloStyleDemo/uicode_dashboard.c
Dashboard 页面展示了一个完整的数据面板,包含 KPI 卡片、折线图、柱状图和饼图。页面进入时,所有数据从零开始”生长”到目标值,形成入场动效。
动效设计¶
动画方式:Timer 驱动,20 帧完成,每帧间隔 40ms(总时长 800ms)
缓动曲线:手动实现减速缓动
t' = 1 - (1-t)^2动画内容:KPI 数字递增 + 图表数据从零生长到目标值
核心实现¶
数据定义和 Timer 初始化:
static egui_timer_t db_growth_timer;
static int db_growth_frame = 0;
#define DB_GROWTH_FRAMES 20
#define DB_GROWTH_INTERVAL 40
// 目标数据
static const int16_t db_line_target_y[] = {20, 45, 30, 60, 40, 75, 55};
static const int16_t db_bar_target_y[] = {30, 50, 40, 70, 60};
static const uint16_t db_pie_target_vals[] = {40, 30, 20, 10};
// 可变的动画数据(从 0 插值到目标)
static egui_chart_point_t db_line_pts_anim[] = {
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0},
};
Timer 回调中的逐帧更新:
static void db_growth_timer_callback(egui_timer_t *timer)
{
(void)timer;
db_growth_frame++;
if (db_growth_frame > DB_GROWTH_FRAMES)
{
egui_view_stop_timer(EGUI_VIEW_OF(&db_title), &db_growth_timer);
return;
}
// 计算减速缓动进度
int progress = (db_growth_frame * 100) / DB_GROWTH_FRAMES;
int t_inv = 100 - progress;
int decel = 100 - (t_inv * t_inv) / 100;
// 更新折线图数据点
for (int i = 0; i < 7; i++)
{
db_line_pts_anim[i].y = (int16_t)((db_line_target_y[i] * decel) / 100);
}
egui_view_chart_line_set_series(EGUI_VIEW_OF(&db_line_chart), db_line_series_anim, 1);
// 更新 KPI 数字显示
db_update_kpi_display(decel);
}
页面进入时重置并启动动画:
void uicode_page_dashboard_on_enter(void)
{
// 重置所有动画数据为零
db_growth_frame = 0;
for (int i = 0; i < 7; i++) db_line_pts_anim[i].y = 0;
for (int i = 0; i < 5; i++) db_bar_pts_anim[i].y = 0;
for (int i = 0; i < 4; i++) db_pie_slices_anim[i].value = 0;
db_pie_slices_anim[0].value = 1; // 饼图至少需要一个非零值
db_update_kpi_display(0);
// 启动生长动画
egui_view_start_timer(EGUI_VIEW_OF(&db_title), &db_growth_timer, DB_GROWTH_INTERVAL, DB_GROWTH_INTERVAL);
}
设计要点¶
每次进入页面都重置状态,确保动画可重复播放
饼图需要保证至少一个 slice 非零,否则渲染异常
减速缓动使数据在前期快速增长、后期缓慢趋近目标,视觉上更自然
一个 Timer 同时驱动 KPI、折线图、柱状图、饼图四类数据的更新
Activity 切换动画¶
源文件:example/HelloActivity/uicode_disp0.c
Activity 是 EmbeddedGUI 的页面管理单元,类似 Android 的 Activity。页面切换时可以配置入场和退场动画。
水平滑动切换¶
新 Activity 从右侧滑入,旧 Activity 向左侧滑出:
// 新页面打开:从屏幕右侧滑入到原位
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_start_open_param,
EGUI_CONFIG_SCREEN_WIDTH, 0, 0, 0);
egui_animation_translate_t anim_start_open;
// 旧页面关闭:从原位滑出到屏幕左侧
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_start_close_param,
0, -EGUI_CONFIG_SCREEN_WIDTH, 0, 0);
egui_animation_translate_t anim_start_close;
// 返回时,旧页面从左侧滑回
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_finish_open_param,
-EGUI_CONFIG_SCREEN_WIDTH, 0, 0, 0);
egui_animation_translate_t anim_finish_open;
// 返回时,当前页面向右滑出
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_finish_close_param,
0, EGUI_CONFIG_SCREEN_WIDTH, 0, 0);
egui_animation_translate_t anim_finish_close;
初始化和注册:
static void setup_activity_anims(egui_activity_t *activity)
{
// 初始化 start_open 动画
egui_animation_translate_init(EGUI_ANIM_OF(&anim_start_open));
egui_animation_translate_params_set(&anim_start_open, &anim_start_open_param);
egui_animation_duration_set(EGUI_ANIM_OF(&anim_start_open), 300);
// 初始化 start_close 动画(需要 fill_before 确保结束后回到初始位置)
egui_animation_translate_init(EGUI_ANIM_OF(&anim_start_close));
egui_animation_translate_params_set(&anim_start_close, &anim_start_close_param);
egui_animation_duration_set(EGUI_ANIM_OF(&anim_start_close), 300);
egui_animation_is_fill_before_set(EGUI_ANIM_OF(&anim_start_close), true);
// ... finish_open 和 finish_close 类似 ...
// 绑定到待启动的 Activity 对象
egui_activity_set_start_anim(
activity,
EGUI_ANIM_OF(&anim_start_open),
EGUI_ANIM_OF(&anim_start_close));
egui_activity_set_finish_anim(
activity,
EGUI_ANIM_OF(&anim_finish_open),
EGUI_ANIM_OF(&anim_finish_close));
}
四个动画的配合关系¶
场景 |
新页面动画 |
旧页面动画 |
|---|---|---|
打开新 Activity |
start_open(右->中) |
start_close(中->左) |
返回上一 Activity |
finish_open(左->中) |
finish_close(中->右) |
fill_before 的作用:退场动画(start_close、finish_close)结束后,需要将 View 恢复到原始位置(偏移量=0),否则下次显示时 View 仍停留在屏幕外。
垂直滑动切换(备选方案)¶
代码中通过 #if 0 保留了垂直方向的切换方案:
// 新页面从底部滑入
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_start_open_param,
0, 0, EGUI_CONFIG_SCREEN_HEIGHT, 0);
// 旧页面向上滑出
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_start_close_param,
0, 0, 0, -EGUI_CONFIG_SCREEN_HEIGHT);
Dialog 弹出动画¶
Dialog 使用从底部上滑的入场动画和向下滑出的退场动画:
// Dialog 入场:从屏幕底部滑到原位
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_dialog_start_param,
0, 0, EGUI_CONFIG_SCREEN_HEIGHT, 0);
egui_animation_translate_t anim_dialog_start;
// Dialog 退场:从原位滑到屏幕底部
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_dialog_finish_param,
0, 0, 0, -EGUI_CONFIG_SCREEN_HEIGHT);
egui_animation_translate_t anim_dialog_finish;
初始化和注册:
// 入场动画
egui_animation_translate_init(EGUI_ANIM_OF(&anim_dialog_start));
egui_animation_translate_params_set(&anim_dialog_start, &anim_dialog_start_param);
egui_animation_duration_set(EGUI_ANIM_OF(&anim_dialog_start), 500);
// 退场动画
egui_animation_translate_init(EGUI_ANIM_OF(&anim_dialog_finish));
egui_animation_translate_params_set(&anim_dialog_finish, &anim_dialog_finish_param);
egui_animation_duration_set(EGUI_ANIM_OF(&anim_dialog_finish), 500);
egui_animation_is_fill_before_set(EGUI_ANIM_OF(&anim_dialog_finish), true);
// 假设 dialog 已经完成 egui_dialog_xxx_init(..., core)
egui_dialog_set_anim(
(egui_dialog_t *)&dialog,
EGUI_ANIM_OF(&anim_dialog_start),
EGUI_ANIM_OF(&anim_dialog_finish));
Dialog 动画时长(500ms)比 Activity 切换(300ms)更长,营造从容的弹出感。
HelloBasic/anim 综合动画演示¶
源文件:example/HelloBasic/anim/test.c
这个示例在一个屏幕上同时展示 4 种动画效果,是学习动画系统的最佳入口。
四列布局¶
列 |
动画类型 |
插值器 |
视觉效果 |
|---|---|---|---|
1 |
Translate |
Bounce |
红色圆形上下弹跳 |
2 |
Alpha |
Linear |
绿色方块匀速淡入淡出 |
3 |
ScaleSize |
Overshoot |
蓝色圆形弹性缩放 |
4 |
AnimationSet |
AccelerateDecelerate |
橙色方块下移+淡出 |
所有动画均设置为无限往返(repeat_count=-1, REVERSE 模式),持续播放。
关键代码片段¶
Translate + Bounce 组合:
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_translate_param,
0, 0, 0, EGUI_CONFIG_SCREEN_HEIGHT - VIEW1_RADIUS * 2);
static egui_animation_translate_t anim_translate;
static egui_interpolator_bounce_t interp_bounce;
egui_animation_translate_init(EGUI_ANIM_OF(&anim_translate));
egui_animation_translate_params_set(&anim_translate, &anim_translate_param);
egui_animation_duration_set(EGUI_ANIM_OF(&anim_translate), 1500);
egui_animation_repeat_count_set(EGUI_ANIM_OF(&anim_translate), -1);
egui_animation_repeat_mode_set(EGUI_ANIM_OF(&anim_translate), EGUI_ANIMATION_REPEAT_MODE_REVERSE);
egui_interpolator_bounce_init((egui_interpolator_t *)&interp_bounce);
egui_animation_interpolator_set(EGUI_ANIM_OF(&anim_translate), (egui_interpolator_t *)&interp_bounce);
egui_animation_target_view_set(EGUI_ANIM_OF(&anim_translate), EGUI_VIEW_OF(&view_translate));
egui_animation_start(EGUI_ANIM_OF(&anim_translate));
AnimationSet 组合动画(第 4 列)展示了如何将 Translate 和 Alpha 合并为一个同步播放的动画组,并通过 mask 机制统一管理属性。