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_x、lv_obj_set_y、lv_obj_set_width 等等。你也可以自己写一个,只要符合这个签名就行。
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 的动画系统其实比想象中要灵活得多。