4. 第一个LVGL程序:创建屏幕、添加标签、理解lv_scr_act()

好,咱们正式开始写代码了。

前面几章我们把LVGL的移植、配置、显示驱动都跑通了。现在屏幕能亮,但上面什么都没有——就像你焊好了一块开发板,但还没烧程序。这一章我们就来点亮它,让屏幕上出现第一行文字。

我个人习惯,学一个新GUI库,第一件事就是看它怎么创建屏幕、怎么往屏幕上放东西。这就像学编程语言先写"Hello World"一样,能帮你最快地理解这个框架的核心流程。

4.1 LVGL的屏幕管理机制

LVGL里,屏幕(Screen)是一个特殊的对象。它本质上就是一个容器,是所有其他控件的根节点。你想想看,你往屏幕上放一个按钮,这个按钮的父对象就是当前的活动屏幕。

LVGL支持多屏幕,但同一时刻只有一个屏幕是"活动"的。所有新创建的控件,默认都会挂到这个活动屏幕上。

核心概念:lv_scr_act() 返回当前活动屏幕的指针。这个函数你会频繁用到,因为它决定了你的控件画在哪个屏幕上。

我记得刚开始用LVGL时,犯过一个低级错误:创建了一个屏幕,然后往上面加标签,结果标签死活不显示。后来才发现,我创建屏幕后忘了调用 lv_scr_load(),新屏幕根本没变成活动屏幕。嗯,这个坑咱们后面细说。

4.2 创建你的第一个屏幕

创建屏幕很简单,就一行代码:

lv_obj_t * scr = lv_obj_create(NULL);

参数传 NULL,表示这个对象没有父对象——它自己就是一个屏幕。LVGL内部会把它当作一个特殊的根节点来处理。

但光创建还不够。你得告诉LVGL:嘿,我现在要用这个屏幕了。所以需要加载它:

lv_scr_load(scr);

这两行代码执行完,你的屏幕上应该就是一片空白——嗯,至少背景色变了。如果你之前移植时设置了默认背景色,现在应该能看到一个纯色的屏幕。

小技巧:创建屏幕时,可以顺便设置它的样式。比如改背景色:lv_obj_set_style_bg_color(scr, lv_color_hex(0x2c3e50), 0);。这样你一眼就能看出屏幕切换成功了。

4.3 添加标签:让屏幕说句话

屏幕有了,但上面空荡荡的。我们来加一个标签(Label),显示"Hello, LVGL!"。

lv_obj_t * label = lv_label_create(scr);
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_center(label);

三行代码,搞定。

  • 第一行:在屏幕 scr 上创建一个标签对象。
  • 第二行:设置标签显示的文本。
  • 第三行:让标签居中显示。这个 lv_obj_center() 是个很方便的函数,它会自动计算父容器的宽高,把子对象放在正中间。

你可能会问:为什么标签的父对象是 scr?因为所有控件创建时,如果不指定父对象,默认会挂到当前活动屏幕上。但这里我们明确指定了父对象是 scr,这样即使之后切换了活动屏幕,这个标签也不会跑偏。

注意:如果你在创建标签之前没有调用 lv_scr_load(scr),那么标签的父对象会是之前那个默认屏幕,而不是你新创建的屏幕。我曾经因为这个折腾了半小时,标签就是不出来——其实它显示在另一个屏幕上了。

4.4 理解 lv_scr_act() 的妙用

lv_scr_act() 是LVGL里一个非常高频的函数。它的返回值是当前活动屏幕的指针。说白了,就是告诉你"现在哪个屏幕是老大"。

什么时候会用到它?

  • 动态创建控件:你想在活动屏幕上临时加个提示框,但又不想记住屏幕指针。
  • 获取屏幕尺寸:比如你想知道当前屏幕的宽高,可以用 lv_obj_get_width(lv_scr_act())。
  • 屏幕切换后的操作:切换屏幕后,用 lv_scr_act() 确认当前是不是你想要的屏幕。

来看一个实际例子:

// 获取当前活动屏幕
lv_obj_t * current_scr = lv_scr_act();

// 在活动屏幕上创建一个按钮
lv_obj_t * btn = lv_btn_create(current_scr);
lv_obj_set_pos(btn, 10, 10);

// 获取屏幕宽度,让按钮居中
lv_coord_t scr_w = lv_obj_get_width(current_scr);
lv_obj_set_x(btn, (scr_w - lv_obj_get_width(btn)) / 2);

你看,有了 lv_scr_act(),你不需要到处传递屏幕指针。尤其是在写一些通用函数时,这个函数能让你少写很多参数。

经验之谈:我建议你在每个屏幕切换的回调里,都打印一下 lv_scr_act() 的地址。调试阶段这能帮你快速定位"控件去哪了"的问题。

4.5 完整代码示例

把上面的代码串起来,就是一个完整的第一个LVGL程序:

#include "lvgl.h"

void my_first_screen(void)
{
    // 1. 创建新屏幕
    lv_obj_t * scr = lv_obj_create(NULL);
    
    // 2. 设置屏幕背景色(深蓝色,护眼)
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x2c3e50), 0);
    
    // 3. 加载屏幕,让它成为活动屏幕
    lv_scr_load(scr);
    
    // 4. 在活动屏幕上创建标签
    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello, LVGL!");
    lv_obj_center(label);
    
    // 5. 再创建一个标签,显示当前屏幕指针地址(调试用)
    lv_obj_t * debug_label = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(debug_label, "scr_act: 0x%08X", (uint32_t)lv_scr_act());
    lv_obj_align(debug_label, LV_ALIGN_TOP_LEFT, 10, 10);
}

int main(void)
{
    lv_init();
    // ... 初始化显示驱动、触摸驱动等 ...
    
    my_first_screen();
    
    while(1) {
        lv_task_handler();
        // 延时,根据你的系统调整
        SDL_Delay(5);
    }
}

这段代码跑起来,你应该能看到:深蓝色的背景上,正中间显示"Hello, LVGL!",左上角还有一行小字显示当前活动屏幕的地址。

4.6 知识体系梳理

这一章的内容其实就三个核心点:创建屏幕、加载屏幕、在屏幕上放控件。但背后涉及了LVGL的对象树结构和活动屏幕机制。我画了一张图帮你理清关系:

LVGL屏幕与对象树结构 LVGL核心 活动屏幕 (lv_scr_act()) 其他屏幕 (非活动) 标签 (Label) 按钮 (Button) 其他控件 (...) 关键说明 • 活动屏幕是当前显示的屏幕 • 新控件默认挂在活动屏幕上 • lv_scr_act() 返回活动屏幕指针

从这张图你能看到:LVGL核心管理着多个屏幕,但只有一个是活动屏幕。所有控件(标签、按钮等)都挂在活动屏幕下面。你通过 lv_scr_act() 拿到活动屏幕的指针,然后就可以往上面添加各种控件了。

4.7 避坑指南

这一章内容不多,但有几个坑我当年都踩过,写出来给你提个醒:

常见问题 原因 解决方法
标签不显示 创建标签时父对象不是活动屏幕 使用 lv_scr_act() 作为父对象
屏幕切换后控件消失 控件创建在旧屏幕上,新屏幕没有加载 切换屏幕前确保所有控件都创建在新屏幕上
lv_scr_act() 返回 NULL LVGL初始化未完成,或没有默认屏幕 检查 lv_init() 和显示驱动初始化顺序
文字显示乱码 字体未正确配置或编码问题 确保使用LVGL内置字体,或正确加载自定义字体

我曾经踩过的坑:有一次我在一个中断服务函数里调用了 lv_label_set_text(),结果程序直接死机。后来才发现,LVGL的大部分API都不是线程安全的,不能在中断里直接调用。正确的做法是发一个消息给主循环,让主循环去更新UI。

4.8 小结

这一章我们完成了第一个LVGL程序。你学会了:

  • 用 lv_obj_create(NULL) 创建屏幕
  • 用 lv_scr_load() 加载屏幕
  • 用 lv_label_create() 和 lv_label_set_text() 添加标签
  • 用 lv_scr_act() 获取当前活动屏幕

这些是LVGL开发最基础的操作。后面每一章都会用到这些知识,尤其是 lv_scr_act(),你会越来越熟悉它。

嗯,代码写完了,编译下载到板子上,看到屏幕上出现"Hello, LVGL!"的那一刻,还是挺有成就感的。接下来,我们会深入控件的样式和布局,让界面变得更好看。