4. LVGL基础架构

好,咱们进入第四章。这一章是整个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的坐标系统其实不复杂。记住三个概念:

  1. 父坐标:相对于父对象的左上角
  2. 屏幕坐标:相对于屏幕的左上角
  3. 内容区域:去掉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 知识体系总览

下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引。

LVGL对象系统与基础控件知识体系 lv_obj_t 对象模型 父子关系 树形结构·层级继承 坐标系统 父坐标·屏幕坐标 基础属性 大小·位置·样式 样式系统 状态·层叠·继承 lv_btn 按钮控件 lv_label 标签控件 5种状态样式 按下/松开/禁用 事件绑定 点击/长按/释放 4种长模式 换行/滚动/省略 文本格式化 字体/颜色/对齐 💡 学习建议 先理解对象模型和父子关系,再动手练习按钮和标签的创建与样式设置

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的骨架,父子关系决定了布局逻辑,坐标系统让你能精确定位,样式让界面好看,按钮和标签是最常用的两个控件。把这些吃透了,后面学其他控件就是换汤不换药。