3、动画创建与启动:lv_anim_init()、lv_anim_start()、lv_anim_set_*()系列函数

好,咱们直接进入正题。动画的创建和启动,说白了就是两件事:配参数按开关。LVGL 把这套流程封装得非常清晰,你只要按部就班调用几个函数就行。我个人习惯把这几个函数分成两组来看:一组是初始化用的 lv_anim_init(),另一组是各种 lv_anim_set_*() 配置函数,最后用 lv_anim_start() 一脚油门踩下去。

3.1 核心流程:先初始化,再配置,最后启动

先看一个最基础的例子,感受一下整个流程:

/* 1. 定义一个动画结构体 */
lv_anim_t a;
/* 2. 初始化 */
lv_anim_init(&a);
/* 3. 配置各种参数 */
lv_anim_set_var(&a, btn);              // 对谁做动画
lv_anim_set_exec_cb(&a, anim_cb);      // 回调函数
lv_anim_set_values(&a, 0, 100);        // 从0到100
lv_anim_set_time(&a, 500);             // 持续500ms
lv_anim_set_path_cb(&a, lv_anim_path_ease_out); // 缓动曲线
/* 4. 启动 */
lv_anim_start(&a);

嗯,这里要注意:lv_anim_init() 必须第一个调用。它会把结构体里的所有成员清零,并设置一些默认值。比如默认的缓动曲线是线性,默认的延时是0。我刚开始用的时候,有次忘了调 lv_anim_init(),结果动画跑起来参数全是乱的,查了半天才发现是结构体里残留了脏数据。

3.2 lv_anim_init():清空画布,打好地基

这个函数其实很简单,它只做一件事:把 lv_anim_t 结构体恢复到出厂设置。原型如下:

void lv_anim_init(lv_anim_t *a);

调用之后,结构体里的所有字段都会被清零。但有几个默认值会被设置:

字段 默认值 说明
time 400 动画时长,单位ms
path_cb lv_anim_path_linear 线性缓动
start_cb NULL 无启动回调
completed_cb NULL 无完成回调

你想想看,如果每次都要手动设置所有参数,那得多麻烦。LVGL 帮你省了这一步,你只需要关注那些需要改动的参数就行。

3.3 lv_anim_set_*():按需配置,灵活组合

这一系列函数是动画配置的核心。我按使用频率把它们分成三类:

3.3.1 必填参数(不设不行)

  • lv_anim_set_var(&a, var):指定动画作用的对象。可以是任何 lv_obj_t * 指针,也可以是自定义结构体。
  • lv_anim_set_exec_cb(&a, cb):设置执行回调。动画每帧都会调用这个函数,你在这里更新对象的属性。
  • lv_anim_set_values(&a, start, end):设置起始值和结束值。注意,这两个值会作为参数传给执行回调。
  • lv_anim_set_time(&a, duration_ms):动画持续时间,单位毫秒。

举个例子,如果你想让一个按钮在1秒内从透明变到完全不透明:

lv_anim_set_var(&a, btn);
lv_anim_set_exec_cb(&a, set_opacity_cb);
lv_anim_set_values(&a, 0, 255);
lv_anim_set_time(&a, 1000);

对应的回调函数长这样:

static void set_opacity_cb(void *var, int32_t v) {
    lv_obj_set_style_opa((lv_obj_t *)var, v, 0);
}

3.3.2 常用可选参数(建议加上)

  • lv_anim_set_path_cb(&a, path_cb):设置缓动曲线。默认是线性,但实际项目中我几乎不用线性,太生硬了。常用的是 lv_anim_path_ease_outlv_anim_path_bounce
  • lv_anim_set_delay(&a, delay_ms):启动前的延迟时间。适合做序列动画,比如让多个按钮依次弹出。
  • lv_anim_set_repeat_count(&a, count):重复次数。设为 LV_ANIM_REPEAT_INFINITE 可以无限循环。
  • lv_anim_set_repeat_delay(&a, delay_ms):每次重复之间的停顿。
我的小技巧: 做列表项入场动画时,我会给每个项设置不同的 delay,比如 0ms、100ms、200ms... 这样看起来就像多米诺骨牌一样依次出现,效果非常流畅。

3.3.3 高级参数(特定场景使用)

  • lv_anim_set_playback_time(&a, time_ms):反向播放的时间。配合 lv_anim_set_playback_delay 可以实现来回摆动效果。
  • lv_anim_set_start_cb(&a, cb):动画启动时的回调。适合做准备工作,比如播放音效。
  • lv_anim_set_completed_cb(&a, cb):动画完成时的回调。我经常用它来做链式动画,一个动画结束后自动启动下一个。
  • lv_anim_set_early_apply(&a, en):是否立即应用起始值。默认是 true,如果你不想让对象在动画开始前就跳到起始值,可以设为 false。

3.4 lv_anim_start():一脚油门,动画起飞

所有参数配好之后,调用 lv_anim_start(&a) 即可启动动画。这个函数会把动画加入 LVGL 的全局动画队列,然后由系统定时器驱动逐帧执行。

这里有个坑,我曾经踩过:动画结构体 lv_anim_t a 必须是全局变量或静态变量,不能是局部变量。 为什么呢?因为 lv_anim_start() 只是把结构体的指针存到了队列里,并没有拷贝一份。如果函数返回后局部变量被销毁,动画运行时就会访问到野指针,直接崩溃。

注意: 如果你用局部变量定义动画结构体,一定要用 lv_anim_t *a = lv_mem_alloc(sizeof(lv_anim_t)); 动态分配内存,并在动画完成后手动释放。或者直接用静态变量。

3.5 完整示例:一个带缓动的缩放动画

最后,给你看一个我在项目中实际用过的例子。这是一个按钮点击后的缩放反馈动画:

static void btn_scale_anim(lv_obj_t *btn) {
    static lv_anim_t a;
    lv_anim_init(&a);

    lv_anim_set_var(&a, btn);
    lv_anim_set_exec_cb(&a, set_scale_cb);
    lv_anim_set_values(&a, 100, 120);  // 缩放百分比
    lv_anim_set_time(&a, 200);
    lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
    lv_anim_set_playback_time(&a, 200);  // 回弹
    lv_anim_set_repeat_count(&a, 1);     // 只来回一次

    lv_anim_start(&a);
}

static void set_scale_cb(void *var, int32_t v) {
    lv_obj_t *obj = (lv_obj_t *)var;
    lv_obj_set_style_transform_scale(obj, v, 0);
}

你看,整个流程非常清晰。初始化、配参数、启动,三步走。我个人觉得 LVGL 的动画 API 设计得相当顺手,比很多嵌入式 GUI 框架都要直观。你只要记住:先 init,再 set,最后 start,就永远不会出错。