4、基础控件(上):按钮、标签、滑块、进度条的创建与事件处理

好,咱们今天来聊聊 LVGL 里最常用的四个基础控件。说实话,这四个控件你玩熟了,中控屏上 80% 的交互逻辑都能搞定。我刚开始做智能家居项目时,就是靠这几个控件搭出了第一版原型。

按钮用来触发动作,标签用来显示文字,滑块用来调节数值,进度条用来反馈状态。嗯,咱们一个一个来。

4.1 按钮(btn)—— 最直接的交互入口

按钮这东西,说白了就是「点一下,有反应」。在 LVGL 里创建按钮非常简单,但我建议你养成好习惯:先创建父容器,再往里面放控件

核心要点:按钮的事件处理是 LVGL 事件机制的入门。你只需要绑定一个回调函数,然后在里面判断事件类型即可。

// 创建一个按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());  // 创建在活动屏幕上
lv_obj_set_size(btn, 120, 50);                // 设置宽高
lv_obj_center(btn);                           // 居中显示

// 给按钮加个标签(文字)
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "点击我");
lv_obj_center(label);

// 绑定事件回调
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

// 回调函数实现
static void btn_event_cb(lv_event_t *e) {
    LV_LOG_USER("按钮被点击了!");
    // 这里可以写你的业务逻辑,比如切换页面、发送指令等
}

我在项目中遇到过一个问题:按钮点击后没有反馈,用户以为没点到,又狂点好几下。后来我加了个点击动画效果,用户体验立马好了很多。

我的小技巧:给按钮添加 LV_STATE_PRESSED 样式,按下时改变背景色或缩放,用户就能「感觉」到点击了。

// 按下时的样式
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_bg_color(&style_pressed, lv_color_hex(0x2196F3));
lv_style_set_transform_zoom(&style_pressed, 512);  // 缩小到 80%

lv_obj_add_style(btn, &style_pressed, LV_STATE_PRESSED);

4.2 标签(label)—— 最基础的文字显示

标签就是用来显示文字的。你想想看,中控屏上哪个界面能少了文字?温度显示、设备名称、状态提示……全是标签的活。

我个人习惯把标签分为三类:静态标签(固定文字)、动态标签(数值变化)、多行标签(长文本)。

类型 适用场景 关键 API
静态标签 标题、图标说明 lv_label_set_text()
动态标签 温度、湿度数值 lv_label_set_text_fmt()
多行标签 日志、提示信息 lv_label_set_long_mode()
// 动态标签示例:显示温度
lv_obj_t *temp_label = lv_label_create(lv_scr_act());
lv_obj_set_pos(temp_label, 20, 30);

// 使用格式化字符串更新数值
float temperature = 25.6;
lv_label_set_text_fmt(temp_label, "当前温度: %.1f°C", temperature);

// 多行标签:自动换行
lv_obj_t *info_label = lv_label_create(lv_scr_act());
lv_label_set_text(info_label, "这是一段较长的提示信息,会自动换行显示。");
lv_label_set_long_mode(info_label, LV_LABEL_LONG_WRAP);  // 自动换行
lv_obj_set_width(info_label, 200);                       // 限制宽度

注意:我曾经在项目里直接用 lv_label_set_text() 更新频繁变化的数值,结果发现内存泄漏了。后来改用 lv_label_set_text_fmt() 配合静态缓冲区,问题就解决了。频繁更新的标签,建议用 lv_label_set_text_static() 绑定静态字符串。

4.3 滑块(slider)—— 调节数值的利器

滑块这东西,说白了就是「拖拽调值」。灯光亮度、音量大小、空调温度……用滑块调节非常直观。

创建滑块时,我建议你注意三个参数:范围初始值步进值。步进值设得太小,拖动时数值跳来跳去,用户体验很差。

// 创建滑块
lv_obj_t *slider = lv_slider_create(lv_scr_act());
lv_obj_set_size(slider, 200, 20);
lv_obj_center(slider);

// 设置范围:0 ~ 100
lv_slider_set_range(slider, 0, 100);
lv_slider_set_value(slider, 50, LV_ANIM_ON);  // 初始值 50,带动画

// 绑定值变化事件
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

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);
    
    // 实际项目中,这里可以发送指令控制设备
    // 比如:set_light_brightness(value);
}

避坑指南:我曾经在调节灯光亮度时,滑块每动一下就发一条 MQTT 消息,结果网络拥堵了。后来我加了个「松手才发送」的逻辑——监听 LV_EVENT_RELEASED 事件,而不是 LV_EVENT_VALUE_CHANGED。嗯,效果立竿见影。

// 松手后才触发
lv_obj_add_event_cb(slider, slider_release_cb, LV_EVENT_RELEASED, NULL);

static void slider_release_cb(lv_event_t *e) {
    lv_obj_t *slider = lv_event_get_target(e);
    int32_t value = lv_slider_get_value(slider);
    // 此时才发送网络指令
    mqtt_publish("home/light/brightness", value);
}

4.4 进度条(bar)—— 直观的状态反馈

进度条和滑块长得很像,但用途完全不同。进度条是只读的,用来显示进度或状态。比如设备加载进度、电量百分比、任务完成度。

我个人习惯把进度条分为两种:确定进度条(知道具体数值)和不确定进度条(只知道在运行,不知道进度)。

// 创建进度条
lv_obj_t *bar = lv_bar_create(lv_scr_act());
lv_obj_set_size(bar, 200, 20);
lv_obj_align(bar, LV_ALIGN_CENTER, 0, 0);

// 设置范围
lv_bar_set_range(bar, 0, 100);

// 设置当前值
lv_bar_set_value(bar, 65, LV_ANIM_ON);  // 带动画效果

// 不确定进度条(加载中动画)
lv_bar_set_mode(bar, LV_BAR_MODE_INDETERMINATE);
// 然后调用 lv_bar_start_anim(bar, 0, 100, 3000);  // 3秒循环一次

实际项目经验:我在做智能门锁界面时,用进度条显示「开锁中」的状态。进度条从 0 走到 100,走完就显示「开锁成功」。用户看着进度条一点点前进,心里就不慌了。这就是 UI 反馈的重要性。

4.5 四个控件的综合应用

好了,咱们把四个控件组合起来,做一个简单的「灯光控制面板」:

  • 标签显示当前亮度数值
  • 滑块调节亮度
  • 进度条显示亮度百分比
  • 按钮用来开关灯
// 创建标签显示数值
lv_obj_t *value_label = lv_label_create(lv_scr_act());
lv_label_set_text(value_label, "亮度: 50%");
lv_obj_align(value_label, LV_ALIGN_TOP_MID, 0, 20);

// 创建滑块
lv_obj_t *slider = lv_slider_create(lv_scr_act());
lv_obj_set_size(slider, 250, 20);
lv_obj_align(slider, LV_ALIGN_CENTER, 0, -30);
lv_slider_set_range(slider, 0, 100);
lv_slider_set_value(slider, 50, LV_ANIM_OFF);

// 创建进度条
lv_obj_t *bar = lv_bar_create(lv_scr_act());
lv_obj_set_size(bar, 250, 20);
lv_obj_align(bar, LV_ALIGN_CENTER, 0, 30);
lv_bar_set_range(bar, 0, 100);
lv_bar_set_value(bar, 50, LV_ANIM_OFF);

// 创建开关按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 100, 40);
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30);
lv_obj_t *btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "开关灯");
lv_obj_center(btn_label);

// 滑块事件:同步更新标签和进度条
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, value_label);
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, bar);

static void slider_event_cb(lv_event_t *e) {
    lv_obj_t *slider = lv_event_get_target(e);
    lv_obj_t *label = (lv_obj_t *)lv_event_get_user_data(e);
    int32_t value = lv_slider_get_value(slider);
    
    // 更新标签
    lv_label_set_text_fmt(label, "亮度: %d%%", (int)value);
    
    // 更新进度条
    lv_bar_set_value(bar, value, LV_ANIM_ON);
}

你看,四个控件配合起来,一个简单的灯光控制界面就出来了。实际项目中,你还可以加上颜色选择、定时开关等功能,但核心交互逻辑就是这些。

最后说一句:基础控件虽然简单,但用好它们需要你对用户交互有理解。我见过很多项目,功能都实现了,但用户就是觉得「不好用」。原因往往出在细节上——动画有没有?反馈及不及时?数值变化是否平滑?这些才是拉开差距的地方。

下一章咱们继续聊剩下的基础控件:开关、下拉列表、复选框和文本框。到时候再结合今天的四个控件,就能搭出更复杂的界面了。