2、动画路径函数:线性路径、缓入缓出、弹跳效果、自定义路径函数
动画路径函数,说白了就是控制动画怎么动起来的规则。你给它一个时间进度(0%到100%),它告诉你动画实际应该走到哪了。这个映射关系,直接决定了动画的“手感”。
我刚开始用LVGL做动画时,觉得默认的线性路径就挺好。后来做智能家居面板,发现所有动画都一个节奏,界面显得特别“死板”。用户滑个菜单,感觉像在推一块铁板。嗯,从那以后我开始认真研究路径函数。
核心概念:路径函数是一个回调,接收当前动画进度(0~1000,LVGL用千分比),返回实际应用值。你可以在 lv_anim_t 结构体中通过 .path_cb 字段设置。
2.1 线性路径:最基础的匀速运动
线性路径最简单,时间走多少,动画就走多少。没有加速,没有减速,全程一个速度。
/* 设置线性路径 */
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_path_cb(&a, lv_anim_path_linear);
/* 或者直接不设置,默认就是线性 */
什么时候用线性?我个人习惯在进度条、加载指示器这类需要“稳定感”的场景用线性。但用在界面切换、弹窗弹出上,就会显得生硬。
我的经验:线性路径适合机械感强的反馈,比如旋钮转动、滑块滑动。但凡是“人机交互”的过渡动画,我建议至少用缓入缓出。
2.2 缓入缓出:让动画有“呼吸感”
缓入缓出模拟了真实物体的运动规律——启动时慢慢加速,快停时慢慢减速。LVGL提供了三种变体:
| 路径函数 | 行为特点 | 适用场景 |
|---|---|---|
lv_anim_path_ease_in |
开始慢,后面快 | 物体离开屏幕、消失动画 |
lv_anim_path_ease_out |
开始快,后面慢 | 物体进入屏幕、出现动画 |
lv_anim_path_ease_in_out |
两头慢,中间快 | 弹窗弹出、页面切换(推荐) |
/* 缓入缓出示例:弹窗从底部滑入 */
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, popup);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_y);
lv_anim_set_values(&a, lv_obj_get_height(popup), 0);
lv_anim_set_time(&a, 300);
lv_anim_set_path_cb(&a, lv_anim_path_ease_out); /* 进入用缓出 */
lv_anim_start(&a);
为什么进入用缓出?你想想看,物体从屏幕外进来,一开始速度最快,人眼还没反应过来;快到位时慢慢减速,用户能清晰看到最终位置。反过来,物体离开时用缓入,加速离开,不拖泥带水。
避坑指南:我曾经在列表项删除动画上用了缓入缓出,结果用户快速删除多个项时,动画还没播完就触发了下一个,导致位置错乱。后来我加了个判断:动画播放期间禁止新操作,或者用 lv_anim_del 强制结束前一个。
2.3 弹跳效果:给界面加点“弹性”
弹跳效果让动画在终点附近来回震荡几次,模拟弹簧或橡皮筋的物理特性。LVGL内置了 lv_anim_path_bounce。
/* 弹跳效果:按钮按下后的回弹 */
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, btn);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_width);
lv_anim_set_values(&a, 100, 120); /* 宽度从100弹到120 */
lv_anim_set_time(&a, 400);
lv_anim_set_path_cb(&a, lv_anim_path_bounce);
lv_anim_start(&a);
弹跳效果很“抢眼”,用多了会显得花哨。我个人只在以下场景用:
- 按钮点击反馈:模拟物理按键的触感
- 通知气泡出现:让用户注意到新消息
- 开关切换:增加操作确认感
小技巧:弹跳动画的时间建议控制在300~500ms。太短弹不起来,太长用户会等得不耐烦。我一般设400ms,配合 lv_anim_set_delay 加个50ms的延迟,效果更自然。
2.4 自定义路径函数:掌控每一帧
内置函数不够用?那就自己写。LVGL允许你传入自定义回调,完全控制动画的进度映射。
/* 自定义路径函数:先快后慢再快(类似“回弹”效果) */
static int32_t my_custom_path(const lv_anim_t *a)
{
/* a->act_time:当前已过时间
* a->time:总时长
* 返回值范围:0~1000(千分比) */
int32_t progress = (a->act_time * 1000) / a->time;
/* 自定义曲线:前30%快速到60%,中间30%慢速到70%,最后40%快速到100% */
if (progress < 300) {
return (progress * 200) / 300; /* 0→200 */
} else if (progress < 600) {
return 200 + ((progress - 300) * 100) / 300; /* 200→300 */
} else {
return 300 + ((progress - 600) * 700) / 400; /* 300→1000 */
}
}
/* 使用自定义路径 */
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_path_cb(&a, my_custom_path);
lv_anim_start(&a);
自定义路径函数接收一个 lv_anim_t * 指针,你可以从中读取当前时间、总时长、起始值、结束值等信息。返回值是0~1000的千分比,表示动画的“虚拟进度”。
重要:自定义路径函数会被频繁调用(每帧一次),所以里面不要做耗时操作。我见过有人在里面做浮点运算和malloc,结果动画卡成PPT。尽量用整数运算,提前算好系数。
我做过一个智能手表项目,表盘切换用了自定义路径——先快速滑动80%,然后慢速“吸附”到最终位置,最后再微弹一下。这个效果用内置函数拼不出来,但自定义路径三行代码就搞定了。
2.5 路径函数选择指南
不同场景该用哪个?我整理了一张速查表:
| 动画类型 | 推荐路径 | 原因 |
|---|---|---|
| 进度条/加载 | 线性 | 稳定、可预测 |
| 页面进入 | 缓出 | 减速到位,视觉舒适 |
| 页面离开 | 缓入 | 加速离开,不拖沓 |
| 弹窗弹出 | 缓入缓出 | 两头慢,中间快,自然 |
| 按钮反馈 | 弹跳 | 增加操作确认感 |
| 特殊效果 | 自定义 | 完全掌控 |
曾经踩过的坑:我在一个列表滚动动画上用了弹跳效果,结果用户快速滑动时,弹跳动画和滚动事件冲突,列表疯狂抖动。后来我改成:滚动惯性用缓出,停止时用自定义路径做一次微小的“过冲回正”,既保留了弹性感,又不会和手势冲突。
路径函数的选择,说白了就是“动画的语感”。线性像机器,缓入缓出像活物,弹跳像调皮的孩子,自定义像你亲手调教的宠物。没有绝对的好坏,只有合不合适的场景。
我个人建议:先默认用缓入缓出,遇到特殊需求再换。不要为了炫技而用弹跳或自定义,用户要的是流畅自然的体验,不是杂技表演。