4、LVGL对象系统与基础控件:lv_obj_t对象模型、父子关系、坐标系统、基础属性(大小、位置、样式)、lv_btn、lv_label
好,咱们进入第四章。这一章是整个LVGL的基石——对象系统。说白了,你在屏幕上看到的每一个按钮、每一段文字、每一个滑块,本质上都是一个lv_obj_t对象。理解了这个模型,后面所有控件你都能举一反三。
我个人习惯把LVGL的对象系统比作一个「乐高积木」体系。每个积木块都有自己的大小、位置、颜色,还能互相嵌套。你想想看,是不是这个道理?
核心认知:LVGL中一切皆对象。lv_obj_t是所有控件的「祖宗」,按钮(lv_btn)、标签(lv_label)都是它的子类。
4.1 lv_obj_t对象模型——一切从这里开始
先看这个结构体长什么样。我不贴完整源码,那太长了。你只需要记住几个关键字段:
| 字段 | 作用 | 我的经验 |
|---|---|---|
parent |
指向父对象的指针 | 决定了对象的层级归属 |
child_ll |
子对象链表 | 所有子对象挂在这里 |
coords |
当前绝对坐标区域 | 系统自动计算,别手动改 |
styles |
样式数组 | 最多支持8个样式层叠 |
flags |
各种标志位 | 比如是否可点击、是否隐藏 |
创建对象的API很简单:
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 在活动屏幕上创建按钮
这里lv_scr_act()返回当前活动屏幕对象,作为父对象。嗯,这里要注意:创建对象时一定要指定父对象,否则对象会变成「孤儿」,显示不出来。我曾经在项目里踩过这个坑,调试了半天才发现是父对象传了NULL。
4.2 父子关系——树形结构才是王道
LVGL的对象组织方式是一棵树。每个屏幕是一个根节点,上面挂着各种容器和控件。父子关系决定了三件事:
- 显示层级:子对象永远显示在父对象之上
- 坐标继承:子对象的坐标是相对于父对象的
- 事件传递:事件可以从子对象冒泡到父对象
看个实际例子:
lv_obj_t * parent = lv_obj_create(lv_scr_act());
lv_obj_set_size(parent, 200, 200);
lv_obj_t * child = lv_obj_create(parent);
lv_obj_set_pos(child, 50, 50); // 相对于parent的(50,50)
lv_obj_set_size(child, 100, 100);
这个child的最终屏幕坐标是(50,50)加上parent的屏幕坐标。你想想看,如果parent移动了,child是不是也跟着动?这就是父子关系的精髓。
我的习惯:尽量保持父子层级不超过3层。层级太深,调试坐标和事件时会很痛苦。我见过有人嵌套了7层,最后自己都搞不清对象在哪。
4.3 坐标系统——搞懂这个,布局就通了
LVGL的坐标系统其实不复杂。记住三个概念:
- 父坐标:相对于父对象的左上角
- 屏幕坐标:相对于屏幕的左上角
- 内容区域:去掉padding后的实际可用区域
坐标相关的API:
lv_obj_set_pos(obj, x, y); // 设置相对于父对象的位置
lv_obj_set_x(obj, x); // 只设置x
lv_obj_set_y(obj, y); // 只设置y
lv_obj_get_pos(obj, &point); // 获取位置
lv_obj_get_coords(obj, &area); // 获取绝对坐标区域
为什么会有一个get_coords?因为有时候你需要知道对象在屏幕上的真实位置,比如做碰撞检测或者自定义绘制时。我个人习惯在调试布局时,用lv_obj_get_coords打印出所有对象的坐标,一目了然。
4.4 基础属性——大小、位置、样式
大小和位置刚才已经提到了。这里重点说说样式。样式是LVGL里最灵活也最容易让人迷惑的部分。
样式可以设置的内容:
| 样式属性 | 示例值 | 说明 |
|---|---|---|
| 背景颜色 | lv_color_hex(0x2196F3) |
支持16位和32位色 |
| 边框 | lv_color_hex(0x1976D2) |
宽度、颜色、圆角 |
| 文字 | lv_color_hex(0xFFFFFF) |
字体、颜色、对齐 |
| 圆角 | 10 |
像素值,0为直角 |
| 透明度 | 128 |
0完全透明,255不透明 |
样式的使用分三步:
// 1. 创建样式
static lv_style_t style;
lv_style_init(&style);
// 2. 设置样式属性
lv_style_set_bg_color(&style, lv_color_hex(0x2196F3));
lv_style_set_radius(&style, 8);
lv_style_set_border_width(&style, 2);
// 3. 应用到对象
lv_obj_add_style(obj, &style, 0); // 0表示默认状态
避坑指南:我曾经在项目里把样式定义成局部变量,结果对象显示出来样式全乱了。样式对象必须保持生命周期有效,要么用static,要么在堆上分配。局部变量一出函数就被销毁了,对象还在引用它,不出问题才怪。
4.5 lv_btn——最基础的交互控件
按钮是LVGL里最简单的交互控件。创建它只需要一行代码:
lv_obj_t * btn = lv_btn_create(lv_scr_act());
但按钮本身是透明的,你需要在它上面加一个标签才能看到文字:
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "点击我");
lv_obj_center(label); // 让标签在按钮中居中
按钮的状态:
- LV_BTN_STATE_RELEASED:松开状态
- LV_BTN_STATE_PRESSED:按下状态
- LV_BTN_STATE_DISABLED:禁用状态
- LV_BTN_STATE_CHECKED_RELEASED:选中松开
- LV_BTN_STATE_CHECKED_PRESSED:选中按下
每个状态都可以设置不同的样式。比如按下时颜色变深:
static lv_style_t style_pr;
lv_style_init(&style_pr);
lv_style_set_bg_color(&style_pr, lv_color_hex(0x1976D2));
lv_obj_add_style(btn, &style_pr, LV_STATE_PRESSED);
4.6 lv_label——显示文本的利器
标签控件用来显示文字。别看它简单,用好了能玩出花来。
基本用法:
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello LVGL!");
lv_obj_set_pos(label, 20, 20);
标签的几种模式:
| 模式 | 宏定义 | 效果 |
|---|---|---|
| 自动换行 | LV_LABEL_LONG_WRAP |
超出宽度自动换行 |
| 滚动 | LV_LABEL_LONG_SCROLL |
文字太长时滚动显示 |
| 省略号 | LV_LABEL_LONG_DOT |
超出部分显示为... |
| 裁剪 | LV_LABEL_LONG_CLIP |
超出部分直接裁剪 |
设置模式:
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
lv_obj_set_width(label, 100); // 设置宽度,触发换行
我个人最喜欢用滚动模式做跑马灯效果,在信息展示类界面里特别实用。
4.7 知识体系总览
下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引。
4.8 实战小练习——组合起来用
光说不练假把式。咱们写一个完整的例子,创建一个带样式的按钮,上面显示文字,点击时改变颜色。
// 创建按钮
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
// 创建标签
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "点我试试");
lv_obj_center(label);
// 定义按下样式
static lv_style_t style_btn_pr;
lv_style_init(&style_btn_pr);
lv_style_set_bg_color(&style_btn_pr, lv_color_hex(0xE53935));
lv_style_set_text_color(&style_btn_pr, lv_color_hex(0xFFFFFF));
// 应用按下样式
lv_obj_add_style(btn, &style_btn_pr, LV_STATE_PRESSED);
// 添加点击事件
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
// 事件回调函数
static void btn_event_cb(lv_event_t * e) {
LV_LOG_USER("按钮被点击了!");
// 这里可以加你的业务逻辑
}
这个例子跑起来,你就能看到一个带交互的按钮了。嗯,这里要注意:事件回调函数里不要做耗时操作,否则界面会卡顿。如果确实需要处理耗时任务,用lv_task或者异步处理。
我的习惯:每个控件创建后,先用lv_obj_align或lv_obj_set_pos明确位置,不要依赖默认布局。默认布局有时候会出乎你的意料,尤其是嵌套层级多的时候。
好了,这一章的内容就到这里。对象模型是LVGL的骨架,父子关系决定了布局逻辑,坐标系统让你能精确定位,样式让界面好看,按钮和标签是最常用的两个控件。把这些吃透了,后面学其他控件就是换汤不换药。