3、创建第一个动画:初始化动画对象、设置动画参数、启动动画、停止动画

好,咱们直接动手。上一章聊了聊 LVGL 动画的基本概念,说白了就是知道了「哦,原来动画就是让一个值随时间变化」。那这一章,我们就来写第一个真正的动画。

我个人习惯,学一个新东西,先跑通一个最简单的例子。别一上来就搞复杂的缓动曲线、路径动画,先把「启动」和「停止」这两个动作玩明白。你想想看,如果连开关都控制不了,后面那些花里胡哨的功能根本没法用。

3.1 初始化动画对象

在 LVGL 里,每个动画都是一个 lv_anim_t 结构体。你可以把它理解成一张「空白的任务卡」。我们需要先声明它,然后填上各种参数。

lv_anim_t my_anim;
lv_anim_init(&my_anim);

嗯,这里要注意:lv_anim_init() 这个函数会把结构体里的所有成员清零,并设置一些默认值。比如默认的动画时长是 0,默认的延迟也是 0。如果你不调用 init,直接设置参数,可能会踩坑——因为未初始化的局部变量里是随机值。

我曾经在项目里吃过这个亏。当时图省事,直接声明了一个 lv_anim_t 就开始设参数,结果动画时长莫名其妙变成了一个巨大的数,动画跑起来慢得像蜗牛。查了半天,才发现是没调用 init,结构体里的 time 成员是个垃圾值。

3.2 设置动画参数

初始化之后,就要往这张「任务卡」里填内容了。核心参数有这几个:

参数 函数 说明
目标对象 lv_anim_set_var() 你要让哪个控件动起来
执行函数 lv_anim_set_exec_cb() 每一帧要修改什么属性(比如坐标、透明度)
起始值 lv_anim_set_values() 从哪个值开始
结束值 lv_anim_set_values() 到哪个值结束
时长 lv_anim_set_time() 动画持续多少毫秒

来,看一个完整的设置例子:

/* 假设我们有一个按钮对象 btn */
lv_anim_set_var(&my_anim, btn);
lv_anim_set_exec_cb(&my_anim, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_set_values(&my_anim, 0, 200);
lv_anim_set_time(&my_anim, 1000);

这段代码的意思是:让按钮 btn 在 1 秒内,从 x=0 的位置移动到 x=200 的位置。

这里有个细节我想强调一下:lv_anim_set_exec_cb() 里的回调函数,必须是接收一个 lv_obj_t * 和一个 int32_t 参数的函数。LVGL 内置了很多这样的函数,比如 lv_obj_set_xlv_obj_set_ylv_obj_set_width 等等。你也可以自己写一个,只要符合这个签名就行。

我建议刚开始学的时候,先用内置的 setter 函数。等后面熟悉了,再自己写回调。这样可以减少调试的复杂度。

3.3 启动动画

参数都填好了,怎么让它跑起来?很简单,调用 lv_anim_start()

lv_anim_start(&my_anim);

就这一行。调用之后,LVGL 的动画引擎会把 my_anim 加入到一个内部的管理队列里。然后每次 LVGL 的 lv_timer_handler() 被调用时,引擎就会检查这个动画,计算当前时间对应的值,并调用你设置的回调函数去更新控件。

说白了,你只管「点火」,引擎帮你「踩油门」。

启动之后,动画会一直运行,直到达到结束值。默认情况下,动画结束后会自动从队列里移除,不需要你手动清理。

3.4 停止动画

有时候动画还没跑完,我们就想让它停下来。比如用户点击了某个按钮,或者某个条件触发了。这时候可以用 lv_anim_del()

lv_anim_del(btn, (lv_anim_exec_xcb_t)lv_obj_set_x);

这个函数的第一个参数是目标对象,第二个参数是执行函数。它会找到所有针对 btn 且回调是 lv_obj_set_x 的动画,然后把它们全部删除。

如果你只想停止某一个特定的动画,而不是所有同类动画,那就要用到 lv_anim_del_all() 或者更精细的控制方式。不过对于初学者,上面这个用法已经够用了。

注意:lv_anim_del() 只是把动画从队列里移除,并不会把控件复位到起始值。也就是说,如果按钮已经移动到 x=150 的位置,你停止动画,它就停在 150,不会自己弹回去。如果你想要复位,需要手动调用 lv_obj_set_x(btn, 0)

3.5 完整的示例代码

好了,我们把上面这些串起来,写一个完整的例子。这个例子会创建一个按钮,点击按钮时启动动画,再点击时停止动画。

static lv_anim_t anim;
static bool anim_running = false;

static void btn_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);

    if(code == LV_EVENT_CLICKED) {
        if(!anim_running) {
            /* 启动动画 */
            lv_anim_init(&anim);
            lv_anim_set_var(&anim, lv_event_get_target(e));
            lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_obj_set_x);
            lv_anim_set_values(&anim, 0, 200);
            lv_anim_set_time(&anim, 1000);
            lv_anim_start(&anim);
            anim_running = true;
        } else {
            /* 停止动画 */
            lv_anim_del(lv_event_get_target(e), (lv_anim_exec_xcb_t)lv_obj_set_x);
            anim_running = false;
        }
    }
}

void my_anim_demo(void)
{
    lv_obj_t * btn = lv_btn_create(lv_scr_act());
    lv_obj_set_pos(btn, 10, 10);
    lv_obj_set_size(btn, 100, 50);
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

    lv_obj_t * label = lv_label_create(btn);
    lv_label_set_text(label, "点击我");
    lv_obj_center(label);
}

这段代码里,我用了一个 anim_running 标志位来记录动画状态。第一次点击按钮,初始化并启动动画;第二次点击,停止动画。这样就能反复测试启动和停止的效果。

核心要点回顾:

  • lv_anim_init(),再设置参数,顺序别搞反。
  • 启动用 lv_anim_start(),停止用 lv_anim_del()
  • 停止动画不会自动复位控件位置,需要手动处理。
  • 回调函数必须符合 void func(lv_obj_t *, int32_t) 的签名。

这一章的内容就到这里。说白了,创建第一个动画就是三步:初始化、填参数、启动。停止动画是附加操作,但也很常用。下一章我们会聊聊动画的更多控制方式,比如延迟、重复、自动反转等等。到时候你会发现,LVGL 的动画系统其实比想象中要灵活得多。