4、LVGL基础控件详解(上):lv_btn、lv_label、lv_img、lv_bar、lv_slider的创建与常用API

控件是LVGL的砖瓦。你想想看,一个界面再花哨,也是由一个个基础控件堆起来的。今天咱们就把最常用的五个控件掰开揉碎讲清楚:按钮、标签、图片、进度条和滑块。

我个人习惯,学新控件先看三样东西:创建函数、核心属性、事件回调。掌握了这三板斧,剩下的都是查文档的事。

本章核心脉络:每个控件都按「创建→属性设置→事件绑定→实战避坑」的顺序展开,方便你快速对照。
LVGL 基础控件知识体系(上) lv_btn 按钮 lv_label 标签 lv_img 图片 lv_bar 进度条 lv_slider 滑块 核心能力:创建 → 属性设置 → 事件绑定 → 样式定制 每个控件都遵循这套流程,一通百通 常用API速查 lv_xxx_create / lv_xxx_set_* / lv_xxx_get_* / lv_obj_add_event_cb ⚠ 避坑重点:内存管理、事件回调上下文、图片格式转换

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_text
lv_label_set_long_mode
通常不需要事件
lv_img lv_img_create(parent) lv_img_set_src
lv_img_set_zoom
LV_EVENT_CLICKED
lv_bar lv_bar_create(parent) lv_bar_set_value
lv_bar_set_range
通常不需要事件
lv_slider lv_slider_create(parent) lv_slider_set_value
lv_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的第一原则。