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核心管理着多个屏幕,但只有一个是活动屏幕。所有控件(标签、按钮等)都挂在活动屏幕下面。你通过 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!"的那一刻,还是挺有成就感的。接下来,我们会深入控件的样式和布局,让界面变得更好看。