4、LVGL基础控件详解(上):lv_btn、lv_label、lv_img、lv_bar、lv_slider的创建与常用API
控件是LVGL的砖瓦。你想想看,一个界面再花哨,也是由一个个基础控件堆起来的。今天咱们就把最常用的五个控件掰开揉碎讲清楚:按钮、标签、图片、进度条和滑块。
我个人习惯,学新控件先看三样东西:创建函数、核心属性、事件回调。掌握了这三板斧,剩下的都是查文档的事。
本章核心脉络:每个控件都按「创建→属性设置→事件绑定→实战避坑」的顺序展开,方便你快速对照。
4.1 lv_btn — 最基础的交互单元
按钮是GUI的敲门砖。你点一下,它就得有反应。创建按钮非常简单:
lv_obj_t *btn = lv_btn_create(lv_scr_act()); // 在当前屏幕创建
lv_obj_set_pos(btn, 50, 50); // 设置位置
lv_obj_set_size(btn, 120, 50); // 设置尺寸
嗯,这里要注意:lv_btn_create的第一个参数是父对象。我见过不少新手直接传NULL,结果按钮显示不出来。父对象可以是屏幕,也可以是容器。
按钮最核心的是事件。我习惯这样绑定点击回调:
static void btn_event_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
LV_LOG_USER("按钮被点击了!");
}
}
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
我的习惯:事件回调里一定要先判断事件码。同一个回调可能被多个事件触发,不加判断容易出bug。我曾经在一个项目里忘了加,结果按钮按下和释放都触发了同一段逻辑,界面直接卡死。
按钮的状态控制也很常用:
lv_obj_add_state(btn, LV_STATE_DISABLED); // 禁用
lv_obj_clear_state(btn, LV_STATE_DISABLED); // 启用
4.2 lv_label — 文本显示的核心
标签是信息输出的主力。创建和设置文本:
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello LVGL");
文本太长怎么办?LVGL提供了几种模式:
| 模式 | 宏定义 | 效果 |
|---|---|---|
| 扩展 | LV_LABEL_LONG_EXPAND |
自动撑大标签(默认) |
| 截断 | LV_LABEL_LONG_DOT |
末尾显示省略号 |
| 滚动 | LV_LABEL_LONG_SCROLL_CIRCULAR |
循环滚动显示 |
| 裁剪 | LV_LABEL_LONG_CLIP |
超出部分直接裁剪 |
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
文本对齐也别忘了:
lv_label_set_recolor(label, true); // 开启颜色解析
lv_label_set_text(label, "#ff0000 红色# 正常文字");
注意:
lv_label_set_recolor开启后,文本中#RRGGBB格式的颜色标记才会生效。但性能会略有下降,大量动态文本时慎用。
4.3 lv_img — 图片显示的正确姿势
图片控件稍微特殊一点。它不能直接加载文件路径,必须先把图片转换成C数组。我常用的转换工具是LVGL官方在线图片转换器,或者用lv_img_conv.py脚本。
转换后会得到一个类似这样的头文件:
const LV_ATTRIBUTE_MEM_ALIGN uint8_t img_logo_map[] = {
0xFF, 0xFF, 0xFF, 0xFF, ...
};
const lv_img_dsc_t img_logo = {
.header.always_zero = 0,
.header.w = 100,
.header.h = 50,
.data_size = sizeof(img_logo_map),
.header.cf = LV_IMG_CF_TRUE_COLOR,
.data = img_logo_map,
};
然后在代码里引用:
lv_obj_t *img = lv_img_create(lv_scr_act());
lv_img_set_src(img, &img_logo);
关键点:
lv_img_set_src可以接受三种类型:const lv_img_dsc_t *(内部数组)、LV_SYMBOL_*(内置符号)、文件路径字符串(需开启文件系统)。
图片缩放和旋转:
lv_img_set_zoom(img, 256); // 256 = 100%,512 = 200%
lv_img_set_angle(img, 900); // 900 = 90度(0.1度为单位)
我记得有一次项目里图片显示不出来,查了半天发现是data_size算错了。转换工具生成的尺寸一定要核对,特别是带透明通道的图片。
4.4 lv_bar — 进度条与状态指示
进度条适合显示百分比、电量、音量等连续值。创建和设置范围:
lv_obj_t *bar = lv_bar_create(lv_scr_act());
lv_bar_set_range(bar, 0, 100); // 范围0~100
lv_bar_set_value(bar, 50, LV_ANIM_ON); // 带动画跳转到50
进度条可以设置起始值和当前值,实现「已用/剩余」效果:
lv_bar_set_start_value(bar, 20, LV_ANIM_OFF);
lv_bar_set_value(bar, 80, LV_ANIM_ON);
模式切换也很实用:
lv_bar_set_mode(bar, LV_BAR_MODE_SYMMETRICAL); // 对称模式,0在中间
实战经验:做音乐播放器进度条时,我建议用
LV_ANIM_ON让进度平滑过渡。但如果是电量显示,用LV_ANIM_OFF更真实——电量不会突然跳变。
4.5 lv_slider — 可拖动的交互控件
滑块本质上是进度条加上拖拽功能。创建方式几乎一样:
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_ON);
滑块最常用的是值改变事件:
static void slider_event_cb(lv_event_t *e) {
lv_obj_t *slider = lv_event_get_target(e);
int32_t val = lv_slider_get_value(slider);
LV_LOG_USER("当前值:%d", val);
}
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
滑块还有一些高级设置:
lv_slider_set_knob_in_left(slider, true); // 旋钮靠左(默认居中)
lv_obj_set_style_anim_time(slider, 200, 0); // 拖动动画时长200ms
避坑指南:我曾经在触摸屏上做滑块,发现拖动不跟手。后来发现是
lv_conf.h里的LV_INDEV_DEF_SCROLL_THROW设得太大了。调小这个值,滑块响应就灵敏多了。
4.6 五个控件的API速查表
| 控件 | 创建函数 | 核心设置API | 常用事件 |
|---|---|---|---|
| lv_btn | lv_btn_create(parent) |
lv_obj_add_state |
LV_EVENT_CLICKED |
| lv_label | lv_label_create(parent) |
lv_label_set_textlv_label_set_long_mode |
通常不需要事件 |
| lv_img | lv_img_create(parent) |
lv_img_set_srclv_img_set_zoom |
LV_EVENT_CLICKED |
| lv_bar | lv_bar_create(parent) |
lv_bar_set_valuelv_bar_set_range |
通常不需要事件 |
| lv_slider | lv_slider_create(parent) |
lv_slider_set_valuelv_slider_set_range |
LV_EVENT_VALUE_CHANGED |
4.7 综合小练习:做一个带进度条的控制面板
光说不练假把式。咱们把今天学的控件串起来,做一个简单的控制面板:
// 创建按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 20, 20);
lv_obj_set_size(btn, 100, 40);
// 按钮上的标签
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "开始");
// 进度条
lv_obj_t *bar = lv_bar_create(lv_scr_act());
lv_obj_set_pos(bar, 20, 80);
lv_obj_set_size(bar, 200, 20);
lv_bar_set_range(bar, 0, 100);
// 滑块
lv_obj_t *slider = lv_slider_create(lv_scr_act());
lv_obj_set_pos(slider, 20, 120);
lv_obj_set_size(slider, 200, 20);
// 滑块控制进度条
static void slider_cb(lv_event_t *e) {
lv_obj_t *sl = lv_event_get_target(e);
lv_obj_t *bar = (lv_obj_t *)lv_event_get_user_data(e);
lv_bar_set_value(bar, lv_slider_get_value(sl), LV_ANIM_ON);
}
lv_obj_add_event_cb(slider, slider_cb, LV_EVENT_VALUE_CHANGED, bar);
你看,代码量不大,但一个完整的交互闭环就有了。滑块拖动,进度条跟着走,按钮还能触发其他动作。这就是LVGL的魅力——组合起来就是完整的UI。
总结一下:今天这五个控件,按钮管交互、标签管文字、图片管视觉、进度条和滑块管数值。它们各自独立又互相配合。你写代码的时候,多想想「这个控件最适合表达什么信息」,而不是「我用哪个控件最炫」。实用至上,这是嵌入式UI的第一原则。