4. LVGL 控件(一):基础控件与事件机制
好,咱们今天来啃 LVGL 的基础控件。说实话,这部分是嵌入式 GUI 的「砖瓦」—— 你后面所有复杂的界面,都是靠这些基础控件搭起来的。我个人习惯先把 Label、Button、Checkbox、Slider 这四个玩透,再去看别的。
为什么?因为它们覆盖了三种最核心的交互:显示文本、触发动作、状态切换与数值调节。你想想看,一个设置界面,是不是就这几样东西来回组合?
本章核心脉络:先掌握每个控件的创建与基本用法,再理解样式如何「叠加」到控件上,最后搞懂事件回调是怎么被触发的。这三层打通了,LVGL 的控件体系你就入门了。
4.1 Label(标签)—— 最基础的显示单元
Label 说白了就是用来显示文字的。但你别小看它,我在项目里见过有人把 Label 当「调试输出窗口」用,结果内存爆了 —— 因为没限制文本长度。
创建 Label 很简单:
/* 创建一个标签 */
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
嗯,这里要注意几个关键属性:
- 文本设置:
lv_label_set_text()会复制字符串,频繁更新时建议用lv_label_set_text_fmt()格式化。 - 长文本处理:默认不换行。需要调用
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP)开启自动换行。 - 文本颜色:通过样式设置,后面会细讲。
我的经验:调试时我喜欢在 Label 里显示系统运行时间或帧率,但记得加个最大长度限制,否则字符串拼接会慢慢吃掉堆内存。
4.2 Button(按钮)—— 触发交互的入口
Button 是用户最常「点」的东西。创建按钮本身不难,难的是让它「好看」且「反馈及时」。
/* 创建按钮 */
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);
Button 有几个状态:LV_STATE_DEFAULT、LV_STATE_PRESSED、LV_STATE_DISABLED 等。每个状态都可以单独设置样式,这就是后面要讲的「样式状态管理」。
避坑指南:我曾经在按钮上直接设置 lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF0000), LV_STATE_DEFAULT),结果发现按下时颜色没变 —— 因为没给 LV_STATE_PRESSED 单独设置样式。记住:每个状态都要单独配样式,否则会继承默认状态。
4.3 Checkbox(复选框)—— 状态切换的利器
Checkbox 比 Button 多了一个「选中状态」。它内部其实是一个小方块 + 一个 Label 的组合。
/* 创建复选框 */
lv_obj_t *cb = lv_checkbox_create(lv_scr_act());
lv_checkbox_set_text(cb, "启用自动调节");
lv_obj_align(cb, LV_ALIGN_CENTER, 0, 0);
/* 设置初始状态为选中 */
lv_obj_add_state(cb, LV_STATE_CHECKED);
Checkbox 的状态判断也很直接:
/* 判断是否选中 */
bool is_checked = lv_obj_has_state(cb, LV_STATE_CHECKED);
我个人习惯把 Checkbox 用于「设置页」的开关选项,比如「开启夜间模式」、「启用日志输出」这类二值选择。
4.4 Slider(滑块)—— 连续数值调节
Slider 是调节数值的直观控件,比如亮度、音量、进度条。它本质上是一个可拖动的条。
/* 创建滑块 */
lv_obj_t *slider = lv_slider_create(lv_scr_act());
lv_obj_set_size(slider, 200, 20);
lv_obj_align(slider, LV_ALIGN_CENTER, 0, 0);
/* 设置范围 */
lv_slider_set_range(slider, 0, 100);
lv_slider_set_value(slider, 50, LV_ANIM_ON);
Slider 有几个关键属性:
| 属性 | 说明 | 常用函数 |
|---|---|---|
| 范围 | 最小值到最大值 | lv_slider_set_range() |
| 当前值 | 滑块所在位置的值 | lv_slider_set_value() |
| 步进 | 拖动时的最小变化单位 | lv_slider_set_step() |
| 动画 | 设置值时是否平滑移动 | 第三个参数传 LV_ANIM_ON/OFF |
我的经验:Slider 的步进值别设太大,否则用户拖动时会感觉「一跳一跳」的。我一般设 1 或 5,具体看你的精度需求。
4.5 控件属性与样式设置
样式是 LVGL 的灵魂。每个控件都可以通过 lv_obj_set_style_* 系列函数来设置外观。
样式设置的核心逻辑是:属性 + 状态 + 优先级。
/* 设置按钮默认背景色为蓝色 */
lv_obj_set_style_bg_color(btn, lv_color_hex(0x3498db), LV_STATE_DEFAULT);
/* 设置按钮按下时背景色为深蓝色 */
lv_obj_set_style_bg_color(btn, lv_color_hex(0x2980b9), LV_STATE_PRESSED);
/* 设置圆角 */
lv_obj_set_style_radius(btn, 8, LV_STATE_DEFAULT);
/* 设置边框 */
lv_obj_set_style_border_width(btn, 2, LV_STATE_DEFAULT);
lv_obj_set_style_border_color(btn, lv_color_hex(0x2c3e50), LV_STATE_DEFAULT);
常用的样式属性包括:
- 背景:
bg_color、bg_opa(透明度)、bg_grad_color(渐变色) - 边框:
border_width、border_color、border_side(哪一侧有边框) - 文本:
text_color、text_font、text_align - 尺寸与位置:
width、height、pad_all(内边距)
核心原则:样式是「叠加」的。子控件会继承父控件的样式,但子控件自己的样式优先级更高。如果你发现某个控件的颜色不对,先检查它有没有被父控件的样式「覆盖」了。
4.6 事件回调机制
事件是用户交互的「桥梁」。没有事件,控件就是一堆静态图形。
LVGL 的事件机制是 冒泡式 的:事件从触发控件开始,逐级向上传递,直到被处理或到达根对象。
/* 定义事件回调函数 */
static void btn_event_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t *target = lv_event_get_target(e);
if (code == LV_EVENT_CLICKED) {
LV_LOG_USER("按钮被点击了!");
/* 在这里处理点击逻辑 */
} else if (code == LV_EVENT_PRESSING) {
/* 按住不放时持续触发 */
} else if (code == LV_EVENT_VALUE_CHANGED) {
/* 值改变时触发(Slider、Checkbox 常用) */
}
}
/* 绑定事件 */
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
常用的事件类型:
| 事件代码 | 触发时机 | 适用控件 |
|---|---|---|
LV_EVENT_CLICKED |
点击后释放 | Button |
LV_EVENT_PRESSED |
按下瞬间 | Button、Slider |
LV_EVENT_VALUE_CHANGED |
值发生变化 | Slider、Checkbox |
LV_EVENT_LONG_PRESSED |
长按超过阈值 | Button |
避坑指南:我曾经在事件回调里直接调用 lv_obj_del() 删除触发事件的控件,结果程序直接崩溃。为什么?因为事件处理完后 LVGL 还要访问那个对象。正确的做法是用 lv_event_send() 延迟删除,或者用 lv_async_call() 把删除操作放到下一帧执行。
对于 Slider 和 Checkbox,事件回调里获取当前值的方法:
static void slider_event_cb(lv_event_t *e) {
lv_obj_t *slider = lv_event_get_target(e);
int32_t value = lv_slider_get_value(slider);
LV_LOG_USER("当前滑块值: %d", value);
}
static void checkbox_event_cb(lv_event_t *e) {
lv_obj_t *cb = lv_event_get_target(e);
bool checked = lv_obj_has_state(cb, LV_STATE_CHECKED);
LV_LOG_USER("复选框状态: %s", checked ? "选中" : "未选中");
}
嗯,到这里基础控件的基本用法就讲完了。你可能会问:「这些控件怎么组合成一个完整的界面?」别急,下一节我们会讲布局和容器,到时候你就知道怎么把它们「装」在一起了。
最后送你一句话:控件是死的,样式是活的,事件是灵魂。把这三点吃透,你就能做出任何想要的界面。