4、LVGL核心控件(一):标签(Label)、按钮(Button)、进度条(Bar)、滑块(Slider)的创建与属性配置
在LVGL图形界面开发中,控件(Widget)是构成用户界面的基本单元。本章聚焦于四个最基础且高频使用的核心控件:标签(Label)、按钮(Button)、进度条(Bar)和滑块(Slider)。这四个控件分别承担着信息展示、用户触发、状态反馈和数值调节的核心职责。理解它们的对象模型、创建流程、属性配置与事件响应机制,是构建复杂物联网设备界面的基石。我们将从LVGL的对象(Object)体系出发,逐一剖析每个控件的核心原理、配置方法、实战场景与常见误区,帮助你在嵌入式资源受限环境下高效构建专业UI。
上图展示了LVGL中四个核心控件与基类 lv_obj_t 的继承关系,以及它们各自的核心职责。所有控件都继承自基础对象,因此具备位置、大小、样式等通用属性。同时,每个控件又扩展了专属的属性和行为。下面我们将深入每个控件的具体实现与配置细节。
4.1 标签(Label):文本显示的核心载体
核心原理
标签控件是LVGL中最简单的显示控件,其本质是一个用于渲染文本的矩形区域。它内部管理着一个文本缓冲区,并负责根据设定的字体、颜色、对齐方式等样式属性将文本绘制到屏幕上。标签本身不响应用户输入,但可以动态修改其文本内容,是界面信息更新的主要手段。在物联网设备中,标签常用于显示传感器读数、设备状态、网络连接信息、时间日期等动态数据。
创建与属性配置操作流程
创建一个标签并配置其核心属性,通常遵循以下步骤:
- 创建对象:调用
lv_label_create(parent)函数,其中parent是指向父对象的指针,通常是当前屏幕(lv_scr_act())或一个容器(Container)。 - 设置文本内容:使用
lv_label_set_text(label, "文本")设置静态文本,或使用lv_label_set_text_fmt(label, "数值: %d", value)设置格式化文本,这对于显示动态数据非常方便。 - 配置长文本模式:当文本过长时,需要设置处理模式。使用
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP)实现自动换行,或LV_LABEL_LONG_SCROLL_CIRCULAR实现循环滚动。 - 设置对齐方式:通过
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0)设置文本在标签区域内的水平对齐方式。 - 调整样式:设置字体、文字颜色、背景色等。例如
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0)设置文字为白色,lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0)设置字体大小。
案例拆解:动态显示MQTT连接状态
假设我们需要在设备屏幕左上角显示MQTT连接状态。初始状态为“正在连接...”,连接成功后变为“已连接”,并显示为绿色;失败则显示“连接失败”并显示为红色。
// 创建标签
lv_obj_t * status_label = lv_label_create(lv_scr_act());
lv_obj_set_pos(status_label, 10, 10);
lv_label_set_text(status_label, "正在连接...");
lv_obj_set_style_text_color(status_label, lv_color_hex(0xFFFF00), 0); // 黄色
// 在MQTT事件回调中更新
void mqtt_event_handler(mqtt_client_t *client, mqtt_event_t event) {
if (event == MQTT_EVENT_CONNECTED) {
lv_label_set_text(status_label, "已连接");
lv_obj_set_style_text_color(status_label, lv_color_hex(0x00FF00), 0); // 绿色
} else if (event == MQTT_EVENT_DISCONNECTED) {
lv_label_set_text(status_label, "连接失败");
lv_obj_set_style_text_color(status_label, lv_color_hex(0xFF0000), 0); // 红色
}
}
此案例展示了标签在状态反馈中的典型应用。通过 lv_label_set_text 和样式修改,实现了界面信息的实时更新。
lv_label_set_text 会涉及内存分配和字符串拷贝。对于高频更新的数据(如每秒刷新一次的传感器值),建议使用 lv_label_set_text_fmt 并配合静态缓冲区,以减少内存碎片。同时,LVGL v8版本中,标签默认不自动调整大小,需要手动设置 lv_obj_set_size 或使用 lv_label_set_long_mode 来适应文本长度。
4.2 按钮(Button):用户交互的触发核心
核心原理
按钮是接收用户点击指令并触发相应逻辑的控件。它基于 lv_obj_t 构建,通过添加事件回调函数来响应用户操作。按钮的核心在于其状态管理(正常、按下、聚焦、禁用)和事件分发机制。在LVGL中,按钮本身不包含文本,通常需要在其内部创建一个标签(Label)作为子对象来显示按钮文字。
创建与事件绑定操作流程
- 创建按钮:
lv_obj_t * btn = lv_btn_create(lv_scr_act()); - 设置按钮尺寸与位置:
lv_obj_set_size(btn, 120, 50);和lv_obj_set_pos(btn, 20, 20); - 添加文本子标签:创建标签并设置为按钮的子对象,然后居中显示。
- 添加事件回调:使用
lv_obj_add_event_cb(btn, event_handler, LV_EVENT_CLICKED, NULL);注册点击事件处理函数。 - 实现回调函数:在回调中编写具体操作逻辑,例如发送MQTT消息、切换界面等。
案例拆解:控制继电器开关
我们需要一个按钮来控制一个LED灯的开关,并通过MQTT上报状态。按钮按下时,切换LED状态并发送消息。
static void btn_event_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
static bool led_state = false;
led_state = !led_state;
// 控制硬件LED
gpio_set_level(LED_GPIO, led_state);
// 通过MQTT上报状态
char payload[20];
snprintf(payload, sizeof(payload), "{\"led\":%s}", led_state ? "true" : "false");
mqtt_publish("device/led/state", payload);
// 更新按钮文本
lv_label_set_text(btn_label, led_state ? "LED: ON" : "LED: OFF");
}
}
// 创建按钮和标签
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
lv_obj_center(btn);
lv_obj_t * btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "LED: OFF");
lv_obj_center(btn_label);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
这个案例完整展示了按钮从创建到事件响应的全过程。关键在于事件回调中实现了硬件控制、网络通信和UI更新的联动。
LV_EVENT_CLICKED,而 LV_EVENT_PRESSED 和 LV_EVENT_RELEASED 分别对应按下和释放的瞬间,可根据需求选择。
4.3 进度条(Bar):状态可视化的重要组件
核心原理
进度条用于直观地展示任务完成度或某个数值的当前比例。它由背景轨道和前景指示器(Indicator)两部分组成。LVGL的进度条支持设置最小值、最大值和当前值,并自动计算指示器的宽度或高度比例。进度条本身不响应用户交互,仅作为显示组件。
创建与属性配置操作流程
- 创建进度条:
lv_obj_t * bar = lv_bar_create(lv_scr_act()); - 设置范围:
lv_bar_set_range(bar, 0, 100);设置最小值和最大值。 - 设置当前值:
lv_bar_set_value(bar, 50, LV_ANIM_ON);设置当前值,并启用动画平滑过渡。 - 设置方向:默认水平,可通过
lv_obj_set_style_transform_angle或使用lv_bar_set_orientation(v9+)调整方向。 - 自定义样式:通过
lv_obj_set_style_bg_color设置轨道颜色,lv_obj_set_style_bg_color(bar, ..., LV_PART_INDICATOR)设置指示器颜色。
案例拆解:显示固件升级进度
在物联网设备OTA升级场景中,进度条是必备元素。我们需要根据接收到的数据包比例实时更新进度条。
// 假设总数据包为 total_packets,已接收 received_packets
void update_ota_progress(uint32_t received, uint32_t total) {
int percent = (received * 100) / total;
lv_bar_set_value(ota_bar, percent, LV_ANIM_ON);
// 同时更新一个标签显示百分比
lv_label_set_text_fmt(percent_label, "%d%%", percent);
}
// 初始化进度条
lv_obj_t * ota_bar = lv_bar_create(lv_scr_act());
lv_obj_set_size(ota_bar, 200, 20);
lv_obj_center(ota_bar);
lv_bar_set_range(ota_bar, 0, 100);
lv_bar_set_value(ota_bar, 0, LV_ANIM_OFF);
此案例展示了进度条在长时间任务中的反馈作用。通过 LV_ANIM_ON 参数,进度条的变化更加平滑,提升了用户体验。
4.4 滑块(Slider):数值调节与交互输入
核心原理
滑块是进度条的交互式扩展,它允许用户通过拖动指示器来改变数值。滑块在进度条的基础上增加了可拖动的旋钮(Knob)和事件响应机制。当用户拖动旋钮时,滑块的值会实时变化,并触发 LV_EVENT_VALUE_CHANGED 事件。滑块常用于调节音量、屏幕亮度、温度设定等需要连续或离散数值输入的场合。
创建与事件处理操作流程
- 创建滑块:
lv_obj_t * slider = lv_slider_create(lv_scr_act()); - 设置范围与初始值:
lv_slider_set_range(slider, 0, 100);和lv_slider_set_value(slider, 30, LV_ANIM_OFF); - 添加事件回调:
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); - 在回调中获取值:使用
lv_slider_get_value(slider)获取当前值,并执行相应操作。 - 自定义样式:可以分别设置轨道、指示器和旋钮的样式,例如
lv_obj_set_style_bg_color(slider, ..., LV_PART_KNOB)。
案例拆解:调节屏幕亮度
我们需要一个滑块来调节设备屏幕的背光亮度,并将调节后的值通过MQTT上报,以便同步到其他设备。
static void slider_event_cb(lv_event_t * e) {
lv_obj_t * slider = lv_event_get_target(e);
int16_t brightness = lv_slider_get_value(slider);
// 映射到硬件PWM范围 (0-1023)
uint32_t pwm_value = (brightness * 1023) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, pwm_value);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
// 通过MQTT上报
char topic[32];
snprintf(topic, sizeof(topic), "device/brightness");
char payload[8];
snprintf(payload, sizeof(payload), "%d", brightness);
mqtt_publish(topic, payload);
}
// 创建滑块
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_OFF);
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
该案例实现了从UI交互到硬件控制再到网络同步的完整链路。滑块的值变化直接驱动了PWM输出,并通过MQTT实现了远程同步。
LV_EVENT_VALUE_CHANGED 事件,如果回调中执行了重量级操作(如网络发送),会导致性能下降和消息风暴。解决方案是使用 LV_EVENT_RELEASED 事件来发送最终值,或者在回调中进行节流(Throttle)处理,例如每50ms最多发送一次。
4.5 控件对比与选择指南
| 控件类型 | 核心用途 | 用户交互 | 关键属性/方法 | 典型应用场景 |
|---|---|---|---|---|
| 标签 (Label) | 显示静态或动态文本 | 无 | lv_label_set_text, lv_label_set_long_mode |
传感器读数、状态信息、时间显示 |
| 按钮 (Button) | 触发用户指令 | 点击/释放 | lv_obj_add_event_cb, LV_EVENT_CLICKED |
开关控制、模式切换、确认操作 |
| 进度条 (Bar) | 显示任务进度或数值比例 | 无 | lv_bar_set_range, lv_bar_set_value |
OTA升级进度、数据下载进度、电量显示 |
| 滑块 (Slider) | 调节连续或离散数值 | 拖动旋钮 | lv_slider_set_range, lv_slider_get_value |
音量调节、亮度调节、温度设定 |
4.6 综合实战:构建一个MQTT灯光控制面板
为了巩固本章知识,我们设计一个综合案例:一个灯光控制面板,包含一个标签显示当前亮度值,一个滑块调节亮度,一个按钮用于开关灯光,以及一个进度条显示灯光“平滑过渡”的动画进度。
界面布局与逻辑:
- 顶部:标签显示“亮度: 50%”。
- 中部:滑块(0-100),用于调节亮度。
- 下部:按钮,点击切换灯光开关状态。
- 底部:进度条,模拟灯光从当前亮度到目标亮度的过渡过程。
核心代码逻辑:
static lv_obj_t * brightness_label;
static lv_obj_t * transition_bar;
static void slider_cb(lv_event_t * e) {
lv_obj_t * slider = lv_event_get_target(e);
int val = lv_slider_get_value(slider);
lv_label_set_text_fmt(brightness_label, "亮度: %d%%", val);
// 模拟过渡动画:进度条从0到目标值
lv_bar_set_value(transition_bar, 0, LV_ANIM_OFF);
lv_bar_set_value(transition_bar, val, LV_ANIM_ON);
// 实际项目中,这里会调用硬件PWM调节
}
static void btn_cb(lv_event_t * e) {
static bool is_on = false;
is_on = !is_on;
// 更新按钮文本和滑块可用状态
// 此处省略具体实现
}
void create_light_panel(void) {
// 创建标签
brightness_label = lv_label_create(lv_scr_act());
lv_obj_set_pos(brightness_label, 10, 10);
lv_label_set_text(brightness_label, "亮度: 50%");
// 创建滑块
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, -20);
lv_slider_set_range(slider, 0, 100);
lv_slider_set_value(slider, 50, LV_ANIM_OFF);
lv_obj_add_event_cb(slider, slider_cb, LV_EVENT_VALUE_CHANGED, NULL);
// 创建按钮
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 40);
lv_obj_t * btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "灯光: ON");
lv_obj_center(btn_label);
lv_obj_add_event_cb(btn, btn_cb, LV_EVENT_CLICKED, NULL);
// 创建进度条
transition_bar = lv_bar_create(lv_scr_act());
lv_obj_set_size(transition_bar, 200, 10);
lv_obj_align(transition_bar, LV_ALIGN_CENTER, 0, 80);
lv_bar_set_range(transition_bar, 0, 100);
lv_bar_set_value(transition_bar, 50, LV_ANIM_OFF);
}
案例复盘:此案例综合运用了本章所有控件。滑块负责输入,标签负责反馈,按钮负责模式切换,进度条负责视觉反馈。通过 LV_ANIM_ON 参数,我们轻松实现了进度条的平滑过渡动画,提升了用户体验。在实际项目中,滑块的 LV_EVENT_VALUE_CHANGED 回调中应避免直接进行硬件PWM的频繁写入,可以结合定时器进行软件PWM控制,或者使用硬件PWM的渐变功能。
lv_obj_set_style_*)和控件特有属性(lv_label_set_*等)。事件回调中只做轻量级操作,避免阻塞UI。对于频繁更新的数据,考虑使用 lv_label_set_text_fmt 和静态缓冲区。最后,善用LVGL的动画机制(LV_ANIM_ON),可以显著提升界面的专业感。