4、动画路径与缓动函数:线性缓动、缓入缓出、弹跳效果、自定义缓动曲线
好,咱们接着聊动画。上一章我们把动画的“骨架”——时长、延迟、回调这些基础属性搞清楚了。这一章,我们来聊聊动画的“灵魂”——路径与缓动。
说白了,缓动函数决定了你的控件怎么动。是匀速直线运动?还是先慢后快?还是像皮球一样弹两下?这些效果,全靠缓动函数来实现。
我个人习惯把缓动函数比作“油门”。你踩死油门(线性),速度恒定;你轻踩再深踩(缓入),加速过程就平滑了。你想想看,如果UI里所有动画都是生硬的匀速运动,那体验得多糟糕。
4.1 线性缓动:最基础,但别滥用
线性缓动,就是lv_anim_path_linear。它的特点是:速度恒定。从起点到终点,每一帧移动的距离都一样。
代码很简单:
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_set_values(&a, 0, 200);
lv_anim_set_time(&a, 1000);
lv_anim_set_path_cb(&a, lv_anim_path_linear); // 线性缓动
lv_anim_start(&a);
嗯,这里要注意:线性缓动在自然界中几乎不存在。任何真实的物理运动,都有加速和减速过程。所以,除非你刻意追求“机械感”或“机器人风格”,否则尽量少用线性缓动。
4.2 缓入与缓出:UI动画的黄金搭档
缓入(Ease In)和缓出(Ease Out),是UI设计里最常用的两种缓动模式。
- 缓入:先慢后快。适合“离开”场景,比如弹窗消失、卡片飞出。
- 缓出:先快后慢。适合“进入”场景,比如弹窗出现、卡片滑入。
- 缓入缓出:两头慢中间快。适合“来回”运动,比如一个控件从A到B再回到A。
LVGL提供了对应的路径回调函数:
| 函数名 | 效果 | 适用场景 |
|---|---|---|
lv_anim_path_ease_in |
先慢后快 | 元素消失、移出屏幕 |
lv_anim_path_ease_out |
先快后慢 | 元素出现、弹窗展开 |
lv_anim_path_ease_in_out |
两头慢中间快 | 来回摆动、循环动画 |
我建议你记住一个原则:“进入用缓出,退出用缓入”。为什么?因为人眼对“突然出现”的东西更敏感。缓出让进入过程显得自然,缓入让退出过程显得不突兀。
4.3 弹跳效果:让UI“活”起来
弹跳效果,就是lv_anim_path_bounce。它模拟了物体落地后反弹、再落地、再反弹……直到停下来的过程。
我记得第一次在LVGL里用弹跳效果时,整个团队都眼前一亮。一个按钮按下去,弹出一个菜单,菜单像皮球一样轻轻弹了两下——那种“活”的感觉,是线性动画永远给不了的。
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_y);
lv_anim_set_values(&a, -100, 100); // 从上方弹入
lv_anim_set_time(&a, 800);
lv_anim_set_path_cb(&a, lv_anim_path_bounce); // 弹跳效果
lv_anim_start(&a);
弹跳效果有几个参数可以调:
- 弹跳次数:默认是3次,你可以通过修改LVGL源码中的
LV_ANIM_BOUNCE_COUNT宏来调整。 - 弹跳幅度:每次反弹的高度递减,递减系数是固定的。如果你想改,就得自己写路径函数了。
4.4 自定义缓动曲线:掌控一切
LVGL内置的缓动函数够用吗?大部分场景够了。但总有一些“刁钻”的需求——比如我想让动画先加速、再匀速、再减速,或者做一个“过冲”效果(超过目标值再拉回来)。这时候,就需要自定义缓动曲线了。
自定义缓动函数,本质上就是写一个回调函数。这个函数的签名是:
lv_coord_t my_custom_path(const lv_anim_t *a);
参数a里包含了当前动画的进度信息。你需要返回一个lv_coord_t类型的值,代表当前帧的位置。
举个例子,我想做一个“过冲”效果——控件先冲到目标值120%的位置,再回弹到100%:
lv_coord_t my_overshoot_path(const lv_anim_t *a) {
// a->act_time: 当前已过去的时间
// a->time: 总时长
// a->start_value: 起始值
// a->end_value: 结束值
int32_t t = a->act_time;
int32_t d = a->time;
int32_t start = a->start_value;
int32_t end = a->end_value;
int32_t range = end - start;
// 进度百分比 (0~1024)
int32_t progress = (t * 1024) / d;
// 过冲公式:先超过目标,再回来
// 这里用了一个简单的三次函数模拟
int32_t overshoot = 0;
if (progress < 512) {
// 前半段:加速冲向目标
overshoot = (progress * progress) / 512;
} else {
// 后半段:超过目标并回弹
int32_t p = progress - 512;
overshoot = 512 + (512 - (p * p) / 512);
}
// 限制范围,防止溢出
if (overshoot > 1024) overshoot = 1024;
if (overshoot < 0) overshoot = 0;
return start + (range * overshoot) / 1024;
}
然后这样使用:
lv_anim_set_path_cb(&a, my_overshoot_path);
我个人习惯把常用的自定义缓动函数封装成一个头文件,比如my_easing.h。这样多个项目都能复用。你想想看,如果每个动画都重新写一遍缓动函数,那得多累。
4.5 实战建议:如何选择缓动函数?
讲了这么多,你可能有点晕:到底该用哪个?
我总结了一个简单的选择指南:
- 控件出现:用
lv_anim_path_ease_out或lv_anim_path_bounce(如果想让效果活泼一点)。 - 控件消失:用
lv_anim_path_ease_in。 - 控件移动:用
lv_anim_path_ease_in_out,让运动看起来更自然。 - 进度条/加载动画:用
lv_anim_path_linear,因为用户期望看到匀速的进度。 - 特殊效果:自定义缓动曲线,比如过冲、弹性、弹簧效果。
lv_anim_path_ease_out。它在大多数场景下表现都不错,属于“万金油”型选手。
好了,这一章的内容就到这里。缓动函数是动画的“调味料”,用好了,你的UI会变得生动、自然、有温度。下一章,我们聊聊动画的组合与队列——如何让多个动画协同工作,实现更复杂的交互效果。