2、LVGL动画核心概念:动画对象、动画变量、动画回调函数、动画时间线
好,咱们直接进入正题。上一章我们让一个方块动了起来,但你可能觉得有点「黑盒」——到底LVGL是怎么控制动画的?这一章,我把动画的四个核心零件拆开给你看。
说白了,LVGL的动画系统就是一套「定时驱动+回调」的机制。你告诉它:谁的什么属性,在多长时间内,从什么值变到什么值,中间要不要插点自定义动作。就这么简单。
2.1 动画对象(lv_anim_t)——动画的「身份证」
每个动画在LVGL里都是一个 lv_anim_t 结构体。你可以把它理解成动画的「档案袋」,里面装着动画的所有配置信息。
我个人习惯用两种方式创建它:
// 方式一:静态变量(推荐,省心)
static lv_anim_t my_anim;
lv_anim_init(&my_anim);
// 方式二:动态分配(用完记得释放)
lv_anim_t * my_anim = lv_anim_create(NULL); // NULL表示不绑定到任何对象
初始化之后,就要往这个「档案袋」里填信息了。核心就是下面这几个字段:
| 字段 | 作用 | 我常踩的坑 |
|---|---|---|
var |
要动画化的对象(比如一个按钮、一个标签) | 忘了赋值,动画直接不执行 |
start_value / end_value |
起始值和结束值 | 注意类型匹配,int和opa_t混用会出怪事 |
duration |
动画持续时间(毫秒) | 设太短(<50ms)人眼几乎看不到 |
exec_cb |
回调函数,每次更新值时调用 | 见2.3节 |
path_cb |
缓动函数,控制运动节奏 | 默认线性,想有弹性效果要自己指定 |
2.2 动画变量——到底在动什么?
你想想看,动画的本质是什么?是「数值随时间变化」。这个数值,就是动画变量。
在LVGL里,动画变量可以是:
- 对象的属性:比如
lv_obj_set_x()的x坐标、lv_obj_set_width()的宽度 - 样式属性:比如
lv_style_set_opa()的透明度、lv_style_set_bg_color()的颜色 - 甚至是你自己定义的变量:比如一个float类型的进度值
我记得有一次做智能家居面板,需要让一个圆环的「角度」随温度变化。LVGL没有现成的角度属性,怎么办?我直接定义了一个 static int16_t angle = 0;,然后在回调里用 lv_img_set_angle(img, angle * 10) 手动更新。嗯,这就是动画变量的灵活之处——它不关心你拿这个值去干嘛,它只负责把值按时给你。
int、float、lv_color_t 变量。只要你在回调函数里把它「用起来」就行。
2.3 动画回调函数——动画的「发动机」
回调函数是动画最核心的部分。LVGL每帧都会调用它,把当前计算好的值传给你。你需要做的,就是把这个值赋给对应的属性或变量。
标准写法是这样的:
static void anim_cb(void * var, int32_t v) {
// var: 你在 lv_anim_set_var() 里传入的对象
// v: 当前帧的动画值(从start_value到end_value之间)
lv_obj_set_x((lv_obj_t *)var, v);
}
// 使用
lv_anim_set_exec_cb(&my_anim, anim_cb);
这里有个细节我当年栽过跟头:回调函数的第二个参数类型是 int32_t。如果你要动画化一个浮点值(比如透明度0.0~1.0),不能直接传float。我的做法是放大100倍:起始值设0,结束值设100,回调里除以100.0f再用。
除了执行回调,还有两个回调也很有用:
- 开始回调(start_cb):动画启动时调用一次。我常用来做「动画开始前禁用按钮」的逻辑。
- 完成回调(completed_cb):动画结束时调用一次。适合做「动画结束后跳转页面」这类操作。
2.4 动画时间线——让多个动画「齐步走」
单个动画好做,难的是多个动画协同。比如一个菜单展开:背景淡入、列表项依次滑入、图标旋转。如果各自用各自的 lv_anim_start(),时间很难对齐。
这时候就需要 动画时间线(lv_anim_timeline) 出场了。
时间线的思路很简单:你创建一个时间线对象,然后把多个动画「挂」上去,每个动画指定一个开始延迟时间。最后统一启动,它们就会按照你设定的时间轴依次执行。
lv_anim_timeline_t * timeline = lv_anim_timeline_create();
// 动画1:背景从透明到不透明,持续300ms,立即开始
lv_anim_t a1;
lv_anim_init(&a1);
lv_anim_set_var(&a1, bg_obj);
lv_anim_set_exec_cb(&a1, bg_opa_cb);
lv_anim_set_values(&a1, 0, 255);
lv_anim_set_time(&a1, 300);
lv_anim_timeline_add(timeline, 0, &a1); // 延迟0ms
// 动画2:列表项从下方滑入,持续400ms,延迟200ms开始
lv_anim_t a2;
lv_anim_init(&a2);
lv_anim_set_var(&a2, list_item);
lv_anim_set_exec_cb(&a2, item_y_cb);
lv_anim_set_values(&a2, 100, 0);
lv_anim_set_time(&a2, 400);
lv_anim_timeline_add(timeline, 200, &a2); // 延迟200ms
// 启动!
lv_anim_timeline_start(timeline);
我个人特别喜欢时间线的一点是:它可以 暂停、继续、反向播放。比如做一个「设置面板」的展开收起效果,展开时正向播放时间线,收起时调用 lv_anim_timeline_set_reverse(timeline, true) 再启动,动画就倒着走了。不用写两套逻辑。
小结一下
这一章我们拆解了动画的四个核心概念:
- lv_anim_t:动画的档案袋,装所有配置
- 动画变量:可以是属性、样式、甚至自定义变量
- 回调函数:每帧执行,把计算好的值应用出去
- 时间线:让多个动画按时间轴协同工作
下一章,我会带你深入缓动函数——为什么有的动画看起来「很舒服」,有的却「很生硬」?秘密就在 path_cb 里。我们到时候细聊。