3. 开发板选型与硬件准备

各位工程师朋友,欢迎来到第三章。这一章我们开始真正动手,接触 LVGL 里最常用的五个基础控件。说实话,我刚开始用 LVGL 的时候,觉得控件嘛,不就是调几个 API 嘛。但真正做项目才发现,每个控件都有它的脾气。用好了,事半功倍;用不好,后面改起来想哭。

这五个控件——标签、按钮、开关、进度条、滑块——几乎覆盖了 80% 的交互场景。你想想看,哪个嵌入式界面能少了它们?

本章核心:掌握每个控件的创建、属性配置、事件绑定,以及我在实际项目中踩过的坑。
LVGL 基础控件知识体系 LVGL 基础控件 lv_label lv_btn lv_switch lv_bar lv_slider 每个控件的核心关注点 • 标签:文本内容、长模式、对齐方式、文本颜色 • 按钮:点击事件、状态切换、样式定制 • 开关:开关状态、动画效果、状态改变回调 • 进度条:范围设置、当前值、动画模式 • 滑块:范围、当前值、拖动事件、步进

3.1 标签(lv_label)—— 最基础的文本显示

标签控件,说白了就是用来显示文字的。但别小看它,我见过太多项目因为标签没用好,导致界面显示乱七八糟。

3.1.1 创建与基本使用

/* 创建一个标签 */
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);

就这么几行,一个标签就出来了。但实际项目中,我们往往需要更复杂的文本处理。

3.1.2 长文本处理

我记得有一次做智能家居面板,需要显示一大段设备状态信息。直接塞进去,结果文字溢出屏幕了。后来才发现,标签有个「长模式」设置。

长模式 宏定义 行为
自动换行 LV_LABEL_LONG_WRAP 超出宽度自动换行
点号省略 LV_LABEL_LONG_DOT 超出部分显示为 ...
滚动显示 LV_LABEL_LONG_SCROLL 文字水平滚动
裁剪 LV_LABEL_LONG_CLIP 超出部分直接裁剪
扩展 LV_LABEL_LONG_EXPAND 自动扩展宽度
/* 设置自动换行 */
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
lv_obj_set_width(label, 200);  /* 设置宽度,触发换行 */
我的建议:做固定区域显示时,优先用 WRAP 或 DOT 模式。SCROLL 模式虽然酷炫,但会占用 CPU 资源,低端 MCU 上慎用。

3.1.3 文本格式化

显示动态数值时,我喜欢用 lv_label_set_text_fmt(),它支持 printf 风格的格式化。

int temperature = 26;
lv_label_set_text_fmt(label, "当前温度:%d°C", temperature);

3.1.4 文本颜色与样式

/* 设置文本颜色 */
lv_obj_set_style_text_color(label, lv_color_hex(0x2196F3), 0);
/* 设置字体大小 */
lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0);
注意:自定义字体需要先注册。我曾经在项目里直接用了未注册的字体,结果界面全乱了。字体文件要提前用 LVGL 的字体转换工具处理。

3.2 按钮(lv_btn)—— 交互的起点

按钮是用户与设备交互最直接的方式。一个按钮按下去,设备就要有反应。

3.2.1 创建按钮

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);

3.2.2 事件处理

按钮的核心就是事件。我习惯用 LV_EVENT_CLICKED 来处理点击。

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);
避坑指南:我曾经在回调函数里做了耗时操作,结果界面卡死了。记住,回调里不要做延时、大循环或者复杂计算。需要的话,用定时器或者任务队列。

3.2.3 按钮状态

按钮有几种状态:正常、按下、禁用、聚焦。你可以为每种状态设置不同的样式。

/* 禁用按钮 */
lv_obj_add_state(btn, LV_STATE_DISABLED);

/* 设置禁用时的背景色 */
lv_obj_set_style_bg_color(btn, lv_color_hex(0xCCCCCC), LV_STATE_DISABLED);

3.3 开关(lv_switch)—— 二选一的优雅方案

开关控件,就是那种可以拨动的滑块。开和关,一目了然。

3.3.1 创建与使用

lv_obj_t *sw = lv_switch_create(lv_scr_act());
lv_obj_align(sw, LV_ALIGN_CENTER, 0, 0);

开关默认是关闭状态。要打开它:

lv_obj_add_state(sw, LV_STATE_CHECKED);

3.3.2 状态监听

static void switch_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_VALUE_CHANGED) {
        bool is_on = lv_obj_has_state(sw, LV_STATE_CHECKED);
        if(is_on) {
            LV_LOG_USER("开关已打开");
        } else {
            LV_LOG_USER("开关已关闭");
        }
    }
}

lv_obj_add_event_cb(sw, switch_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
个人经验:开关的状态变化事件 LV_EVENT_VALUE_CHANGED 比 LV_EVENT_CLICKED 更可靠。因为用户可能通过键盘或触摸板操作,而 CLICKED 只响应点击。

3.3.3 自定义颜色

/* 开启状态的颜色 */
lv_obj_set_style_bg_color(sw, lv_palette_main(LV_PALETTE_GREEN), LV_STATE_CHECKED);
/* 关闭状态的颜色 */
lv_obj_set_style_bg_color(sw, lv_palette_main(LV_PALETTE_GREY), 0);

3.4 进度条(lv_bar)—— 进度可视化

进度条用来显示任务的完成度。比如下载进度、音量大小、电池电量。

3.4.1 创建与设置

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, 50, LV_ANIM_ON);

注意最后一个参数 LV_ANIM_ON,它会让进度条平滑过渡。我个人非常喜欢这个特性,用户体验瞬间提升。

3.4.2 模式设置

进度条有三种模式:

模式 宏定义 用途
普通模式 LV_BAR_MODE_NORMAL 单值显示,最常用
对称模式 LV_BAR_MODE_SYMMETRICAL 以零点为中心,正负值对称显示
范围模式 LV_BAR_MODE_RANGE 显示起始值和结束值之间的范围
/* 范围模式示例:显示已用和剩余 */
lv_bar_set_mode(bar, LV_BAR_MODE_RANGE);
lv_bar_set_start_value(bar, 20, LV_ANIM_OFF);
lv_bar_set_value(bar, 80, LV_ANIM_ON);
注意:范围模式下,起始值必须小于结束值。我曾经传反了,进度条显示异常,排查了半天才发现。

3.5 滑块(lv_slider)—— 可拖动的进度条

滑块和进度条长得很像,但滑块是可以拖动的。用户通过拖动来调整数值。

3.5.1 创建与配置

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, 30, LV_ANIM_ON);

3.5.2 拖动事件

static void slider_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_VALUE_CHANGED) {
        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);

3.5.3 步进设置

有时候我们希望滑块按固定步长跳动,而不是连续变化。

/* 设置步长为 10 */
lv_slider_set_range(slider, 0, 100);
/* 在事件回调中处理步进 */
static void slider_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_VALUE_CHANGED) {
        int32_t val = lv_slider_get_value(slider);
        /* 四舍五入到最近的 10 的倍数 */
        val = (val + 5) / 10 * 10;
        lv_slider_set_value(slider, val, LV_ANIM_OFF);
    }
}
我的习惯:做音量调节时,步进设为 10 或 5 比较合理。用户拖动时手感更好,不会出现 73 这种奇怪的数值。

3.5.4 滑块与进度条的区别

特性 进度条(lv_bar) 滑块(lv_slider)
用户交互 只读,不可拖动 可拖动,可交互
主要用途 显示进度、状态 参数调节、数值选择
事件 通常不需要事件 需要 VALUE_CHANGED 事件
样式 只有指示条 有滑块按钮(旋钮)

3.6 综合示例:一个简单的设置界面

最后,我把这五个控件组合起来,做一个简单的设置界面。你可以在自己的项目里直接套用这个结构。

/* 创建设置界面 */
void settings_ui_create(void) {
    /* 标题标签 */
    lv_obj_t *title = lv_label_create(lv_scr_act());
    lv_label_set_text(title, "设备设置");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);

    /* 音量标签 */
    lv_obj_t *vol_label = lv_label_create(lv_scr_act());
    lv_label_set_text(vol_label, "音量");
    lv_obj_align(vol_label, LV_ALIGN_TOP_LEFT, 20, 80);

    /* 音量滑块 */
    lv_obj_t *vol_slider = lv_slider_create(lv_scr_act());
    lv_obj_set_size(vol_slider, 200, 20);
    lv_slider_set_range(vol_slider, 0, 100);
    lv_slider_set_value(vol_slider, 50, LV_ANIM_OFF);
    lv_obj_align(vol_slider, LV_ALIGN_TOP_LEFT, 80, 75);

    /* 开关标签 */
    lv_obj_t *sw_label = lv_label_create(lv_scr_act());
    lv_label_set_text(sw_label, "Wi-Fi");
    lv_obj_align(sw_label, LV_ALIGN_TOP_LEFT, 20, 130);

    /* Wi-Fi 开关 */
    lv_obj_t *wifi_sw = lv_switch_create(lv_scr_act());
    lv_obj_align(wifi_sw, LV_ALIGN_TOP_LEFT, 80, 125);

    /* 进度条标签 */
    lv_obj_t *prog_label = lv_label_create(lv_scr_act());
    lv_label_set_text(prog_label, "存储");
    lv_obj_align(prog_label, LV_ALIGN_TOP_LEFT, 20, 180);

    /* 存储进度条 */
    lv_obj_t *storage_bar = lv_bar_create(lv_scr_act());
    lv_obj_set_size(storage_bar, 200, 20);
    lv_bar_set_range(storage_bar, 0, 100);
    lv_bar_set_value(storage_bar, 65, LV_ANIM_ON);
    lv_obj_align(storage_bar, LV_ALIGN_TOP_LEFT, 80, 175);

    /* 确认按钮 */
    lv_obj_t *confirm_btn = lv_btn_create(lv_scr_act());
    lv_obj_set_size(confirm_btn, 120, 40);
    lv_obj_align(confirm_btn, LV_ALIGN_BOTTOM_MID, 0, -30);
    lv_obj_t *btn_label = lv_label_create(confirm_btn);
    lv_label_set_text(btn_label, "保存");
    lv_obj_center(btn_label);
}

这个例子涵盖了本章所有控件。你把它跑起来,就能看到一个完整的设置界面。嗯,代码不长,但五脏俱全。