1、LVGL基础回顾:LVGL简介、对象树与父子关系、事件处理机制、样式系统概述

1.1 LVGL是什么?我为什么选它?

LVGL,全称Light and Versatile Graphics Library。说白了,就是一个专门为嵌入式设备打造的图形库。

我最早接触LVGL是在一个智能家居面板项目上。那时候MCU资源紧张,RAM只有64KB,Flash 256KB。用传统GUI方案?想都别想。但LVGL跑起来,流畅得让我意外。

它的核心优势就三点:

  • 轻量:最小配置下,Flash占用不到100KB,RAM几十KB就能跑
  • 丰富:内置30+控件,按钮、滑块、图表、键盘……基本覆盖日常需求
  • 可定制:样式系统极其灵活,你可以把任何控件改得面目全非

嗯,这里要注意:LVGL不是万能的。它适合资源受限的嵌入式场景,但如果你要做复杂3D动画,那还是得找别的方案。

1.2 对象树与父子关系——UI的骨架

LVGL里所有可见元素都是对象(lv_obj_t)。它们组织成一棵树,这就是对象树。

你想想看,一个屏幕(lv_scr_act())是根节点。上面放一个面板(lv_obj),面板里放按钮(lv_btn),按钮里放标签(lv_label)。这就是典型的父子关系。

核心规则:

  • 子对象的位置、大小,默认是相对于父对象的
  • 子对象会继承父对象的某些样式属性
  • 父对象被删除,所有子对象也会被自动删除

我在项目中遇到过一个问题:创建了上百个按钮,每个都手动设置位置。后来发现,只要把按钮放到一个容器(lv_obj)里,统一设置容器的布局(lv_layout),代码量直接减少80%。

看个简单例子:

// 创建父对象(屏幕)
lv_obj_t * scr = lv_scr_act();

// 创建面板作为子对象
lv_obj_t * panel = lv_obj_create(scr);
lv_obj_set_size(panel, 200, 150);
lv_obj_center(panel);

// 在面板里创建按钮
lv_obj_t * btn = lv_btn_create(panel);
lv_obj_set_size(btn, 80, 40);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);

看到没?btn的父对象是panel,不是scr。所以btn的位置是相对于panel的。这就是父子关系的精髓。

我的习惯:每个功能模块创建一个独立的容器对象。这样管理起来清晰,调试也方便。你可以在LVGL的调试工具里看到完整的对象树结构。

1.3 事件处理机制——UI的灵魂

UI没有事件响应,那就是一张图片。LVGL的事件机制,说白了就是「谁发生了什么事,谁来处理」。

事件类型很多,常用的有:

事件类型 触发时机 典型场景
LV_EVENT_CLICKED 点击后释放 按钮点击
LV_EVENT_PRESSING 正在按下 长按检测
LV_EVENT_VALUE_CHANGED 值改变 滑块拖动
LV_EVENT_RELEASED 手指抬起 触摸结束

事件绑定的方式很简单:

static void btn_event_cb(lv_event_t * e) {
    lv_event_code_t code = lv_event_get_code(e);
    
    if(code == LV_EVENT_CLICKED) {
        LV_LOG_USER("按钮被点击了!");
        // 这里写你的业务逻辑
    }
}

// 绑定事件
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

我曾经踩过一个坑:在事件回调里直接修改了正在处理事件的控件属性,结果导致死循环。后来我养成了一个习惯——事件回调里只做标记,真正的逻辑放到主循环里处理。

注意:事件回调中不要做耗时操作,比如延时、大量计算。否则UI会卡死。如果必须做,请使用lv_timer或异步处理。

1.4 样式系统概述——UI的外衣

LVGL的样式系统,是我见过嵌入式GUI里最灵活的。它不像某些库那样用CSS,而是用C语言的结构体来定义样式。

一个样式(lv_style_t)可以设置:

  • 背景颜色、渐变、透明度
  • 边框宽度、颜色、圆角
  • 文字颜色、字体、对齐方式
  • 阴影、轮廓、内边距
  • ……总共100+属性

样式的核心概念是「叠加」。你可以给一个对象添加多个样式,每个样式有不同的优先级。优先级高的覆盖优先级低的。

// 创建样式
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x2196F3));
lv_style_set_radius(&style_btn, 8);
lv_style_set_text_color(&style_btn, lv_color_white());

// 应用到按钮
lv_obj_add_style(btn, &style_btn, 0);

这里有个细节:lv_obj_add_style的第三个参数是选择器(selector)。0表示默认状态。你还可以设置LV_STATE_PRESSED(按下状态)、LV_STATE_DISABLED(禁用状态)等。

我记得有个项目需要按钮按下时颜色变深。一开始我直接在事件回调里改颜色,后来发现用样式状态切换更优雅:

static lv_style_t style_btn_pr;
lv_style_init(&style_btn_pr);
lv_style_set_bg_color(&style_btn_pr, lv_color_hex(0x1565C0));

// 按下时应用这个样式
lv_obj_add_style(btn, &style_btn_pr, LV_STATE_PRESSED);

你看,两行代码搞定。不用写任何事件处理逻辑。

我的建议:把样式定义成全局静态变量,在初始化时统一配置。这样后期改主题色,只需要改一个地方。别像我早期那样,每个控件单独设置颜色,改起来想哭。

1.5 小结

这一章我们回顾了LVGL的四个基础概念:

  • 对象树:UI的骨架,父子关系决定布局和生命周期
  • 事件机制:UI的灵魂,回调函数处理用户交互
  • 样式系统:UI的外衣,灵活叠加,状态切换

这些是后续自定义控件开发的地基。地基打不牢,后面盖楼会歪。下一章,我们开始动手搭建第一个自定义控件。

嗯,如果你现在对LVGL还不太熟,建议先拿官方示例跑一跑。我当年就是一边看文档一边改demo,慢慢就上手了。