4. 主流嵌入式GUI框架选型

LVGL 里的一切,都是对象。你看到的按钮、标签、滑块、进度条……底层全是 lv_obj_t。我刚开始接触 LVGL 时,总觉得这概念有点虚,直到自己动手画了一个按钮,才明白——对象就是一块画布,样式就是画笔和颜料。

这一章,咱们就把这两件事彻底说清楚。

4.1 lv_obj_t 对象模型

每个控件都是一个 lv_obj_t 结构体。它不关心你画的是什么,只负责三件事:

  • 父子关系:对象可以嵌套,子对象跟随父对象移动、裁剪。
  • 位置与大小:坐标、宽高、对齐方式。
  • 事件与样式:谁点了它?它长什么样?

创建对象的套路很简单:

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_scr_act() 返回当前活动屏幕。所有对象都得挂在一个父对象上,屏幕就是最顶层的父对象。

我的习惯:创建复杂界面时,我会先画一张对象树草图。父容器、子容器、控件层级一目了然。省得后面调布局调到头秃。

4.2 基本属性:大小、位置、颜色

对象的基本属性,说白了就三组 API:

属性 常用函数 说明
大小 lv_obj_set_width/height/size 单位是像素,支持百分比
位置 lv_obj_set_pos/x/y/align 相对父对象坐标,或对齐方式
颜色 lv_obj_set_style_bg_color 通过样式设置,不是直接赋值

举个例子:

lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Click Me");
lv_obj_center(label);  // 相对于父对象居中

你可能会问:为什么颜色不能直接 lv_obj_set_color?

因为 LVGL 把颜色归到了样式系统里。对象本身不存颜色,它只存一个指向样式的指针。这样设计的好处是——多个对象可以共用同一个样式,改一处就全变了。

我曾经踩过的坑:直接修改了某个对象的背景色,结果发现同组按钮全变了。后来才意识到,它们共用了一个样式指针。解决方案很简单:用 lv_style_copy 或 lv_obj_add_style 时指定局部样式。

4.3 样式系统:lv_style_t

样式是 LVGL 的灵魂。一个 lv_style_t 可以控制:

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

使用样式的标准流程:

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_border_width(&style_btn, 2);
lv_style_set_border_color(&style_btn, lv_color_hex(0x1976D2));

lv_obj_add_style(btn, &style_btn, 0);  // 0 表示默认状态

这里有个关键点:lv_obj_add_style 的第三个参数是状态。你可以为同一个对象的不同状态设置不同样式:

状态 宏定义 典型用途
默认 LV_STATE_DEFAULT 普通显示
按下 LV_STATE_PRESSED 按钮被按下时变色
聚焦 LV_STATE_FOCUSED 键盘/遥控器选中时高亮
禁用 LV_STATE_DISABLED 变灰不可操作

你想想看,一个按钮在不同状态下自动切换样式,是不是比你自己写 if-else 去改颜色优雅多了?

4.4 主题定制:让界面风格统一

项目做到后期,最怕什么?最怕每个工程师各写各的样式。按钮 A 圆角 6px,按钮 B 圆角 8px,标签字体大小还不一样……

主题就是来解决这个问题的。LVGL 支持两种主题方式:

  1. 内置主题:lv_theme_default_init,改几个颜色就能用。
  2. 自定义主题:自己写回调函数,统一创建样式。

我一般用自定义主题,因为控制力更强。大致思路:

static lv_theme_t * my_theme;
static lv_style_t style_btn_theme;

void my_theme_init(void) {
    lv_style_init(&style_btn_theme);
    lv_style_set_bg_color(&style_btn_theme, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_radius(&style_btn_theme, 4);

    // 注册主题回调
    my_theme = lv_theme_my_init(
        lv_disp_get_default(),
        lv_palette_main(LV_PALETTE_BLUE),
        lv_palette_main(LV_PALETTE_GREY),
        NULL
    );
    lv_disp_set_theme(lv_disp_get_default(), my_theme);
}

之后所有新创建的按钮,都会自动应用主题里的样式。你只需要在主题里定义一次,全局生效。

核心思路:主题 = 一组预定义的样式集合。它不直接修改对象,而是作为对象的默认样式。对象可以覆盖主题样式,但主题保证了整体一致性。

4.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

LVGL 对象与样式知识体系 lv_obj_t 对象 基本属性 样式系统 lv_style_t 主题定制 大小 / 位置 颜色 状态切换 样式继承 内置 / 自定义主题 核心关系 • 对象拥有基本属性,但颜色等视觉属性由样式控制 • 样式可以按状态切换,主题为全局提供默认样式 • 三者配合:对象 → 样式 → 主题,逐层统一

4.6 避坑指南与实战建议

最后,分享几个我实际项目中遇到的教训:

  • 样式变量必须是静态或全局的:局部变量在函数退出后会被销毁,对象指针就变成了野指针。我见过有人把样式定义在函数里,结果界面一刷新就花屏。
  • 不要滥用 lv_obj_set_style_* 系列函数:每次调用都会产生一次样式更新开销。批量设置时,先改样式对象,再一次性 lv_obj_add_style 效率更高。
  • 主题优先级低于局部样式:如果你在对象上单独设置了样式,它会覆盖主题里的同名属性。这是故意的——让你在统一中保留灵活性。
  • 调试时用 lv_style_dump:打印出当前样式的所有属性值,排查问题非常方便。
我的小技巧:在项目初期就定义好主题色板。主色、辅色、警告色、背景色各一个宏。后面所有样式都引用这些宏,改主题时只改一处,全界面跟着变。