4、动画控制API:lv_anim_start、lv_anim_set、lv_anim_del、动画暂停与恢复

动画写好了,怎么让它跑起来?跑起来之后怎么改?怎么停?

这几个问题,说白了就是动画控制API的使用。我刚开始用LVGL做动画时,以为把参数配好就完事了。结果发现,实际项目中动画的启停、修改、删除才是真正考验功底的地方。尤其是产品要响应各种用户操作和系统事件,动画控制不到位,界面就会乱成一锅粥。

4.1 启动动画:lv_anim_start

这是最常用的API,没有之一。它的作用就是让一个已经初始化好的动画对象开始执行。

lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_exec_cb(&a, set_x_cb);
lv_anim_set_values(&a, 0, 200);
lv_anim_set_time(&a, 1000);
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);

lv_anim_start(&a);  // 启动动画

这里有个细节:lv_anim_start调用之后,动画对象a会被LVGL内部接管。你不需要再手动维护它。我见过不少新手在启动后还去修改a的成员,结果导致动画行为异常。

我的习惯:启动动画后,把局部变量a视为已失效。如果需要后续控制,用lv_anim_get获取动画指针,或者用lv_anim_set_user_data绑定回调。

4.2 动态修改:lv_anim_set_* 系列

动画已经在跑了,突然需求变了——比如用户拖动滑块,动画目标值要跟着变。这时候就需要lv_anim_set_*系列函数。

函数 作用 使用场景
lv_anim_set_values 修改起始值和结束值 目标位置动态变化
lv_anim_set_time 修改动画时长 根据系统负载调整速度
lv_anim_set_delay 修改启动延迟 连锁动画的时序调整
lv_anim_set_path_cb 修改缓动曲线 交互反馈风格切换

举个例子,一个进度条动画,用户中途改变了目标值:

// 假设动画正在运行,目标值从0到100
// 用户突然把目标改为80
lv_anim_t *a = lv_anim_get(obj, set_value_cb);
if(a) {
    lv_anim_set_values(a, lv_anim_get_current_value(a), 80);
    // 注意:起始值用当前值,而不是初始值
}
我曾经踩过的坑:修改lv_anim_set_values时,起始值一定要用lv_anim_get_current_value获取当前实际值。如果写成固定值,动画会从旧值跳变到新值,产生视觉闪烁。

4.3 删除动画:lv_anim_del

动画不需要了,或者对象要被销毁了,必须把动画删掉。否则LVGL内部还在持续更新一个已经不存在的对象,轻则内存泄漏,重则崩溃。

// 删除指定对象上的所有动画
lv_anim_del(obj, NULL);

// 删除指定对象上特定回调的动画
lv_anim_del(obj, set_x_cb);

第二个参数传NULL时,删除该对象绑定的所有动画。传具体回调函数时,只删除匹配的那个。我建议在对象析构或页面切换时,统一调用lv_anim_del(obj, NULL)做清理。

关键点:删除动画后,动画对象内部资源会被释放。如果之前用lv_anim_get获取过指针,删除后该指针就悬空了,不要再使用。

4.4 暂停与恢复

暂停和恢复是动画控制里最容易被忽视的功能。你想想看,用户切换到后台页面,动画还在跑,这不是浪费CPU吗?

LVGL没有直接提供lv_anim_pause和lv_anim_resume这样的API。但我们可以通过lv_anim_set_playtime和lv_anim_set_early_apply组合实现类似效果。

更常用的做法是:

// 暂停:设置播放时间为当前已播放时间,并停止
lv_anim_t *a = lv_anim_get(obj, set_x_cb);
if(a) {
    uint32_t played = lv_anim_get_playtime(a);
    lv_anim_set_playtime(a, played);
    lv_anim_del(a->var, a->exec_cb);  // 删除后重新创建时用这个时间
}

// 恢复:用保存的播放时间重新创建动画
lv_anim_t a_new;
lv_anim_init(&a_new);
// ... 设置参数 ...
lv_anim_set_playtime(&a_new, saved_playtime);
lv_anim_start(&a_new);

说实话,这种方式有点绕。我在实际项目中更推荐用lv_anim_set_user_data绑定一个状态标志:

typedef struct {
    bool paused;
    uint32_t saved_playtime;
} anim_user_data_t;

// 在动画执行回调中检查暂停标志
static void set_x_cb(void *obj, int32_t v) {
    anim_user_data_t *ud = lv_anim_get_user_data(lv_anim_get(obj, set_x_cb));
    if(ud && ud->paused) return;
    lv_obj_set_x(obj, v);
}
我的经验:暂停动画时,不要真的删除动画对象。用标志位让执行回调直接返回,这样恢复时只需要改标志位,不需要重新创建动画,性能开销小得多。

4.5 知识体系总览

下面这张图把动画控制API的调用关系和生命周期梳理清楚了:

动画控制API核心流程 lv_anim_init lv_anim_start 动画运行中 lv_anim_set_* 暂停(标志位) lv_anim_del 恢复(清除标志位) 资源释放 暂停/恢复通过用户数据标志位实现,避免频繁创建和删除动画对象

4.6 实战建议

  • 启动动画后不要保留局部引用——LVGL内部会管理,你只需要在需要控制时用lv_anim_get查找。
  • 修改动画参数时,起始值用当前值——否则会出现跳变,用户体验极差。
  • 页面切换或对象销毁前,务必调用lv_anim_del——这是防止内存泄漏和野指针的基本功。
  • 暂停用标志位,不要删除动画——恢复时只需要改标志位,省去重新创建的开销。

记住一句话:动画控制API的核心不是“怎么让动画动起来”,而是“怎么让动画在正确的时间、以正确的方式停下来或改变”。

我在多个项目中验证过,把暂停/恢复用标志位实现,配合lv_anim_set_*动态修改参数,可以覆盖95%以上的交互场景。剩下的5%,用lv_anim_del加重新创建也能搞定。