LVGL:基础对象
一:对象
在 LVGL 中,⽤⼾界⾯的 基本组成部分是对象(控件),也称为 Widgets 。
例如,⼀个 按钮、标签、图像、 列表、图表或者⽂本区域。
所有的对象都使⽤ lv_obj_t 指针作为句柄进⾏引⽤。之后可以使⽤该指针来设置或获取对象的属性。
二:基础对象( lv_obj )
基础对象 ( 控件 ) 实现了屏幕上控件的基本属性,例如:
坐标 ,⽗对象 ,基于⽗对象的后代 ,包含样式 ,诸如 Clickable 、 Scrollable 等属性
在⾯向对象的思想中,基础对象 是 LVGL 中所有其他对象都继承的基类。
基础对象的功能可以与其他控件⼀起使⽤。
例如 lv_obj_set_width(slider, 100) 基础对象可以直接⽤作⼀个简单的控件:它只不过是⼀个矩形。
屏幕是没有⽗对象的基础对象
lv_screen_active()(void); // 活动屏幕(screen_active)
lv_layer_top(void); // 顶层(top layer)
lv_layer_sys(void); // 系统层(system layer)
// 可以在不同层上创建对象(控件)
lv_obj_create(lv_screen_active());
lv_obj_create(lv_layer_top());
lv_obj_create(lv_layer_sys());
学会创建对象 lv_obj_create();
三:基础对象的大小
1. 设置大小(Set Size)
设置宽度: lv_obj_set_width(obj, new_width);
设置⾼度: lv_obj_set_height(obj, new_height);
同时设置宽度、⾼度: lv_obj_set_size(obj, new_width, new_height);
2.获取大小(Get Size)
获取实际宽度: lv_obj_get_width(obj);
获取实际⾼度: lv_obj_get_height(obj);
获取实际可⽤的宽度: lv_obj_get_content_width(obj);
获取实际可⽤的⾼度: lv_obj_get_content_height(obj);
获取实际可⽤的宽⾼区域: lv_obj_get_content_coords(obj);
在 LVGL(嵌入式 GUI 库)中,"实际可用的宽度 / 高度 / 区域" 指的是组件(对象)内部真正可以用来显示内容的空间大小
实际可用的也就是,中间的绿色部分。(盒子模型)
四:LVGL 对象的位置
LVGL 屏幕的坐标系和我们熟悉的坐标系不⼀样, LVGL 的坐标系是我们⼀般称之为 “LCD 坐标系 ” ,他的原点位 置和直⻆坐标系的不⼀样(坐标原点在左上⻆,⽔平向右为 x 轴正⽅向,竖直向下为 y 轴正⽅向 ** )
其实我们只需要记住⼀点: LVGL 的原点位置在左上⻆。
屏幕区域的表⽰(假设屏幕是 1024*600 分辨率):
1.设置位置 (Set Position)
设置x轴方向的坐标位置:lv_obj_set_x(obj,new_x);
设置y轴方向的坐标位置:lv_obj_set_y(obj,new_y);
同时设置x,y坐标位置:lv_obj_set_pos(obj,new_x,new_y);
2. 设置对齐(Align)
参照父对象对齐:lv_obj_set_align(obj, LV_ALIGN_CENTER);//这是中心对齐
第二个参数为对齐类型,根据图标进行选择
参照父对象对齐后再设置坐标位置:lv_obj_align(obj, LV_ALIGN_CENTER, x, y);
obj
:要对齐的目标组件(对象)LV_ALIGN_CENTER
:对齐参考点(枚举值)LV_ALIGN_CENTER
表示 "以父对象的中心为参考点"- 其他常用参考点:
LV_ALIGN_TOP_LEFT
(父对象左上角)、LV_ALIGN_BOTTOM_RIGHT
(父对象右下角)等
x
:在参考点基础上的 X 轴偏移量(像素),正数向右,负数向左y
:在参考点基础上的 Y 轴偏移量(像素),正数向下,负数向上
参照另一个对象(无父子关系)对齐后设置坐标:lv_obj_align_to(obj_to_align, obj_reference, LV_ALIGN_..., x, y);
obj_to_align
:需要被对齐的目标对象(要移动的组件)obj_reference
:作为参考的对象(基准组件)LV_ALIGN_...
:对齐方式(枚举值),指定两个对象的对齐点- 例如
LV_ALIGN_RIGHT_MID
表示 "目标对象的左中对齐参考对象的右中" - 其他常用值:
LV_ALIGN_BOTTOM_LEFT
(目标对象上左对齐参考对象下左)、LV_ALIGN_TOP_RIGHT
(目标对象下右对齐参考对象上右)等
- 例如
x
:对齐后的 X 轴额外偏移量(像素)y
:对齐后的 Y 轴额外偏移量(像素)
3. 获取位置 (Get Position)
获取 x 轴坐标位置 ( 在⽗对象之内 ) :
lv_obj_get_x(obj);
lv_obj_get_x2(obj);
获取 y 轴坐标位置 ( 在⽗对象之内 ) :
lv_obj_get_y(obj);
lv_obj_get_y2(obj);
获取 x 轴坐标位置 ( 对⻬之后 ) : lv_obj_get_x_aligned(obj);
获取 y 轴坐标位置 ( 对⻬之后 ) : lv_obj_get_y_aligned(obj);
五:LVGL盒子模型
LVGL 遵循 CSS 的 border-box 模型。
对象的 “ 盒⼦ ” 由以下部分构成:
边界 (bounding) :元素的宽度 / ⾼度围起来的区域 ( 整个盒⼦ ) 。
轮廓 (outline) :盒⼦之间的距离,确认代之的是轮廓 (outline) 。它是绘制于元素 ( 盒⼦ ) 周围的⼀条线,它不 占据空间,位于边框边缘的外围,可起到突出元素 ( 盒⼦ ) 的作⽤。在浏览器⾥,当⿏标点击或使⽤ Tab 键让⼀ 个选项或者⼀个图⽚获得焦点的时候,这个元素就会多了⼀个轮廓框围绕。轮廓 (outline) 。
边框 (border) :边框有⼤⼩和颜⾊等属性 ( 相当于盒⼦的厚度和它的颜⾊ ) 。
填充 (padding) :对象两侧与其⼦对象之间的空间 ( 盒⼦的填充物 ) 。
内容 (content) :如果边界框按边框宽度和填充的⼤⼩缩⼩,则显⽰其⼤⼩的内容区域 ( 盒⼦实际装东西的区 域 ) 。
LVGL 的盒⼦模型是我们理解对象 ( 部件 ) 的组成,修改对象的样式,实现对对象的布局、处理对象排列等等的关键。
- 当你设置对象的宽⾼(如
lv_obj_set_size(obj, 100, 50)
)时,这个值包含了边框和填充,⽽不仅是内容区域。 - 内容区域的实际⼤⼩ = 边界宽⾼ - 边框宽度 ×2 - 填充 ×2。
假设⼀个对象设置为:
- 宽⾼:100px × 50px(边界⼤⼩)
- 边框宽度:2px(左右 / 上下各 2px)
- 填充:左右各 10px,上下各 5px
- 内容区域宽度 = 100px - 2px×2(边框) - 10px×2(填充) = 76px
- 内容区域⾼度 = 50px - 2px×2(边框) - 5px×2(填充) = 36px
六:对象的样式( Styles )
在 lvgl 中 styles ⽤于设置对象的外观:
样式是⼀个 lv_style_t 变量,它可以保存边框宽度、⽂本颜⾊等属性。
将样式 ( 变量 ) 分配给对象就可以改变其外观。在赋值过程中,可以指定⽬标部分和⽬标状态。
⼀个样式可以给多个对象使⽤(正常样式)。
样式可以级联,也就是可以将多个样式分配给⼀个对象。所以,我们不⽤将所有属性都在⼀个样式中指定, 可以通过多个样式组合的形式指定。
LVGL 会优先使⽤我们定义的样式,如果没有就会使⽤默认值。 后来添加的样式具有更⾼的优先级。也就是说如果在两种样式中指定了同⼀个属性,则将使⽤最后添加的样 式。
如果对象中未指定某些属性(例如⽂本颜⾊),就会从⽗级继承。
上⾯说的是 “ 正常 ” 样式,对象还有本地样式,它⽐ “ 正常 ” 样式具有更⾼的优先级。 可以定义有过渡效果的样式。
默认有⼀个样式主题,我们也可以⾃⼰定义样式主题,作为默认的样式主题使⽤。
1. 样式的使⽤
初始化样式样式存储在 lv_style_t 变量中。
样式变量应该是 静态 、全局或动态分配 的。 也就是它们不能是函数中的局部变量,因为当函数结束时它们会被销毁。样式初始化⽰例:
static lv_style_t style_obj;
lv_style_init(&style_obj);
2.设置样式属性
初始化后,就可以设置属性
lv_style_set_<property_name>(&style, <value>);
lv_style_set_<property_name>
:函数名的前缀固定为lv_style_set_
,后面接具体的样式属性名(如bg_color
表示背景色,border_width
表示边框宽度)。&style
:指向lv_style_t
类型变量的指针,即要修改的样式对象。<value>
:该样式属性的具体值(根据属性类型不同而变化,如颜色值、数值、枚举等)。
示例:
lv_style_set_bg_color(&style_obj, lv_color_hex(0x000000)); // 设置背景色lv_style_set_text_color(&style_obj, lv_color_hex(0xc43e1c)); // 设置文字颜色
3. 添加(应用)样式到对象
lv_obj_add_style(obj, &style,<selector> )
obj
:要应用样式的目标对象(如按钮、容器等)。&style
:指向已初始化的lv_style_t
样式变量的指针,即要应用的样式。<selector>
:样式选择器,用于指定样式生效的对象状态或部件,常用值包括:LV_STATE_DEFAULT
:默认状态(未被点击、未选中时)LV_STATE_PRESSED
:被按下时的状态LV_STATE_CHECKED
:被选中时的状态(如复选框勾选后)LV_STATE_FOCUSED
:获得焦点时的状态(如通过键盘选中)LV_PART_MAIN
:对象的主要部分(默认值,可省略)- 其他部件(如
LV_PART_INDICATOR
用于滑块的指示器部分)
示例:
lv_obj_add_style(obj, &style_obj, 0); // 默认状态: LV_STATE_DEFAULT
lv_obj_add_style(obj, &style_obj, LV_STATE_PRESSED); // 按下状态,当对象被按下的时候应用该样式
4. 获取样式属性
我们可以获取属性的最终值(考虑级联、继承、本地样式和转换),接⼝函数是这样的格式: lv_obj_get_style_<property_name>(obj, <part>);
lv_obj_get_style_<property_name>
:函数名前缀固定为lv_obj_get_style_
,后面接具体的样式属性名(如bg_color
表示背景色,border_width
表示边框宽度)。obj
:要查询的目标对象(如按钮、标签等)。<part>
:指定对象的部件(如LV_PART_MAIN
表示主部件),复杂对象可能包含多个部件(如滑块的轨道、指示器等)。- 返回值:该样式属性的当前生效值(类型与属性对应,如颜色值、数值等)
函数使⽤对象的当前状态,如果没有更好的候选对象,则返回默认值。
示例:
lv_color_t color = lv_obj_get_style_bg_color(obj, LV_PART_MAIN);//获取对象主部件当前生效的背景色
5. 删除样式
删除对象的所有样式: lv_obj_remove_style_all(obj);
删除对象的特定样式: lv_obj_remove_style(obj, &style_obj, selector);
6. 本地样式
除了 “ 普通 ” 样式外,对象还可以存储 本地样式 ( 私有样式 ) 。
本地样式与普通样式类似,但是它不能在其他对象之间共享。如果使⽤本地样式,将⾃动分配局部样式,并在删除对象时释放。
本地样式对于向对象添加本地⾃定义很有⽤。 本地样式的接⼝函数是这样的格式:
lv_obj_set_style_<property_name>(lv_obj_t *obj, <value>, lv_part_t part);
示例:
lv_obj_set_style_bg_color(parent, lv_color_hex(0xc43e1c), 0); // 通过本地样式(私有样式)设置背景色
删除本地样式的时候我们只删除某⼀个样式:
lv_obj_remove_local_style_prop(obj, LV_STYLE_..., selector);
示例:
lv_obj_remove_local_style_prop(parent, LV_STYLE_BG_COLOR, 0); // 删除通过本地样式(私有样式)设置的背景色
七:LVGL 对象的事件
什么是事件?
当发⽣⽤⼾可能感兴趣的事情时, LVGL 中会触发事件并作出相应的处理(反馈),例如当⼀个对象:
被点击 ,滚动 ,数值改变 ,重绘 ,等等。
1. 使用事件
1). 添加事件: lv_obj_add_event_cb(obj, event_cb, event_code, user_data);
2). 发送事件 lv_obj_send_event(obj, event_code, param);
3). 删除事件 lv_obj_remove_event_cb(obj, event_cb);
lv_obj_remove_event_dsc(obj, event_dsc); //event_dsc 是 lv_obj_add_event_cb 返回的指针
示例:
lv_obj_add_event_cb(obj1, my_event_cb, LV_EVENT_ALL, label);//将给obj1添加事件回调函数,所有的事件类型都能触发该回调函数
2.事件类型 (event_code)
输⼊设备事件 (Input device events)
绘图事件 (Drawing events)
其他事件 (Special events)
特殊事件 (Other events)
⾃定义事件 (Custom events)
3.事件回调函数的 lv_event_t 参数
事件回调函数只有⼀个参数,函数原型如下: static void my_event_cb(lv_event_t * e);
这个参数包含了很多信息,其中我们常⽤的有 :
获取触发的事件代码: lv_event_code_t code = lv_event_get_code(e);
获取触发事件的对象: lv_obj_t * target = lv_event_get_target(e);
获取最初触发事件的对象 ( 事件冒泡 ) : lv_obj_t * target = lv_event_get_current_target(e);
获取事件传递的⽤⼾数据:
lv_event_get_user_data(e); 获取使⽤ lv_obj_add_event_cb 函数传递的⽤⼾数据 lv_event_get_param(e); 获取使⽤ lv_obj_send_event 函数传递的⽤⼾数据
4.事件与对象之间的关系
⼀个事件回调函数可给多个对象使⽤。
我们创建了⼀个事件处理函数之后是可以给不同的对象使⽤的。
⼀个对象可以使⽤多个事件回调函数
我们创建的对象可以绑定多个事件,⽐如⼀个事件是处理点击类型的事件,⼀个事件处理按下类型的 事件等等。
如果传⼊的⽤⼾数据不⼀样,⼀个对象可以绑定同⼀个事件回调函数多次,事件将按照添加的顺序调⽤。例如:
lv_obj_add_event_cb(obj, my_clicked_event_cb, LV_EVENT_CLICKED, &num1); lv_obj_add_event_cb(obj, my_clicked_event_cb, LV_EVENT_CLICKED, &num2);
5.事件冒泡
如果对象启⽤了 lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE) ,该对象的所有事件将会发送到该对象的⽗级。如果⽗级也启⽤了 LV_OBJ_FLAG_EVENT_BUBBLE ,那么事件继续发送到他的⽗级,依此类推。
lv_event_get_target(e); 获取触发事件的当前对象。
lv_event_get_current_target(e); 获取事件冒泡的⽗对象
八:LVGL 定时器( lv_timer )
LVGL 的定时器就是会按照指定周期(单位:毫秒 ms )执⾏的函数。
LVGL 有⼀个内置的计时器系统。我们可以注册⼀个函数,让它定期被调⽤,这个函数我们可以称之为定时器处理任务。
这些定时器任务在 lv_task_handler() 中进⾏处理和调⽤,需要每隔 x 毫秒调⽤⼀次。
定时器是⾮抢占式的,这也就是说⼀个定时器不能中断另⼀个定时器。
因此,我们可以在定时器回调函数中调⽤任 何与 LVGL 相关或⽆关的函数。
1. 创建定时器:
lv_timer_t * timer = lv_timer_create(timer_cb, period_ms, user_data);
可以通过 lv_timer_create 返回的定时器句柄 ”timer“ 变量,修改定时器的参数。
timer_cb :定时器回调函数原型: void (*lv_timer_cb_t)(lv_timer_t *)
period_ms :定时器执⾏周期,以毫秒 (ms) 为单位
user_data :⽤⼾数据, void * 类型的指针
示例:
//定时器每 100 毫秒触发一次,定时器触发时会调用回调函数 my_timer1。
timer1 = lv_timer_create(my_timer1, 100, &user_data);
static void my_timer1(lv_timer_t * timer)
{
int *user_data = lv_timer_get_user_data(timer);
uint32_t idle = lv_timer_get_idle();
LV_LOG_USER("my_timer1 user_data: %d, idle: %d", *user_data, idle);
}
2. 准备与重置定时器
lv_timer_ready(timer); //使计时器在下次调用lv_timer_handler()时运行。
lv_timer_reset(timer);//重置计时器的周期。在定义的毫秒周期过去后,它将再次被调用
示例:
先看默认情况(不调用这两个函数):
定时器创建后,会按照固定周期执行:
- 0ms:创建定时器,开始计时
- 1000ms:触发
timer_cb
(第一次执行) - 2000ms:触发
timer_cb
(第二次执行) - 3000ms:触发
timer_cb
(第三次执行) - ... 以此类推,每 1 秒执行一次
1. lv_timer_ready(timer)
:立即执行一次回调,不打乱原有周期
作用:不管当前计时到了多少,立刻调用立即执行一次回调,之后仍然按原周期继续计时。
示例时间线:
- 0ms:创建定时器,开始计时(原本应在 1000ms 第一次执行)
- 500ms:调用
lv_timer_ready(timer)
→ 立即执行timer_cb
(提前执行) - 1000ms:按照原周期,再次执行
timer_cb
(第二次执行) - 2000ms:第三次执行
- ... 后续仍每 1 秒执行一次
2. lv_timer_reset(timer)
:重置计时,延后下一次执行
作用:不触发回调,但会把 “下一次执行时间” 从现在重新计算,相当于 “推迟” 下一次执行。
示例时间线:
- 0ms:创建定时器,开始计时(原本应在 1000ms 第一次执行)
- 800ms:此时距离下一次执行只剩 200ms,调用
lv_timer_reset(timer)
→ 重置计时 - 1800ms:(从 800ms 开始再算 1000ms)第一次执行
timer_cb
- 2800ms:第二次执行
- ... 后续每 1 秒执行一次
3. 设置定时器的参数
lv_timer_set_cb(timer, new_cb); // 设置新的回调函数
lv_timer_set_period(timer, new_period); // 设置新的时间周期
lv_timer_set_user_data(timer, new_user_data); // 设置新的⽤⼾数据
4. 设置重复次数
使⽤ lv_timer_set_repeat_count(timer, count) 接⼝让指定的定时器指定重复的次数。
当定时器调⽤了定义的次数后,它会⾃动被删除。将计数设置为 -1 表⽰⽆限重复。
5. 启⽤和禁⽤定时器使⽤ lv_timer_enable(en) 来启⽤或禁⽤定时器。
6. 暂停和恢复
lv_timer_pause(timer); // 暂停指定的定时器。
lv_timer_resume(timer); // 恢复指定的定时器。
其他用法:
可以通过 lv_timer_create_basic() 在不指定任何参数的情况下创建⼀个新定时器
可以使⽤ lv_timer_get_idle 函数获取 lv_task_handler() 函数的空闲百分⽐时间。
请注意,它并不测量整个 系统的空闲时间,仅测量 lv_task_handler() 的空闲时间。 如果在操作系统中使⽤ lvgl 定时器调⽤ lv_task_handler() ,这可能会产⽣误导,因为它实际上不能测量操作系统在空闲线程中的消耗时间。
问题:
如何删除一个定时器?
void lv_timer_delete(lv_timer_t * timer)//删除指定定时器
如果创建了⼀个控件,⽐如 label ;还创建了⼀个 timer ,并且将前⾯创建的 label 作为⽤⼾数据传递给这个 timer ,然后在这个 timer 的回调函数中这个 label 进⾏了处理,⽐如刷新显⽰某些数据;那么如何合理、合法 ( 指针 ) 地删除这个 timer ?
关键原则:
- 删除顺序:必须先删除定时器,再删除被其引用的控件(如
label
)。如果先删控件,定时器回调可能仍会访问已释放的内存(导致崩溃)。 - 指针置空:删除后将指针设为
NULL
,后续代码可通过判断NULL
避免操作野指针。 - 回调检查:在定时器回调中,先判断用户数据(如
label
)是否为NULL
,再执行操作(防止删除过程中仍有回调触发)。
https://lvgl.100ask.net/9.1/overview/timer.html
百问网:学习记录