跳转到内容

LVGL UI 设计基础

LVGL UI 设计基础

本节介绍使用 LVGL 设计智能家居界面 UI 的基础知识。学习完成后,您将能够:

  • 理解 LVGL 的对象系统和层级结构
  • 创建按钮、标签、滑块、开关等基础控件
  • 应用样式和动画美化界面
  • 设计适合智能家居场景的页面布局

在开始本节之前,请确保:

  • 已完成 LVGL 安装和配置
  • 了解基本的 UI/UX 设计概念
  • ESP32 + 屏幕硬件已正常工作

所有 LVGL 控件都是 lv_obj_t 类型的对象,通过父子关系组织:

// 创建控件的基本模式
lv_obj_t* obj = lv_obj_create(parent); // 创建
lv_obj_set_xxx(obj, value); // 设置属性
lv_obj_set_style_xxx(obj, value, part); // 设置样式
lv_obj_add_event_cb(obj, callback, code, NULL); // 绑定事件
// 创建
lv_obj_t* screen = lv_obj_create(NULL); // 创建屏幕(顶级对象)
lv_obj_t* btn = lv_btn_create(screen); // 创建按钮(屏幕子对象)
lv_obj_t* label = lv_label_create(btn); // 创建标签(按钮子对象)
// 修改
lv_obj_set_pos(btn, 50, 100); // 设置位置
lv_obj_set_size(btn, 120, 50); // 设置大小
// 销毁
lv_obj_del(btn); // 删除对象及其子对象
lv_obj_clean(screen); // 清空所有子对象

用于显示文字信息:

// 温度标签
lv_obj_t* tempLabel = lv_label_create(screen);
lv_label_set_text(tempLabel, "24.5°C");
lv_obj_set_style_text_font(tempLabel, &lv_font_montserrat_32, 0);
lv_obj_set_style_text_color(tempLabel, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(tempLabel, LV_ALIGN_TOP_LEFT, 20, 20);
// 房间名称标签
lv_obj_t* roomLabel = lv_label_create(screen);
lv_label_set_text(roomLabel, "客厅");
lv_obj_set_style_text_font(roomLabel, &lv_font_montserrat_16, 0);
lv_obj_align_to(roomLabel, tempLabel, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 10);

用于触发操作:

// 创建按钮
lv_obj_t* lightBtn = lv_btn_create(screen);
lv_obj_set_size(lightBtn, 120, 60);
lv_obj_set_pos(lightBtn, 40, 100);
// 按钮样式
lv_obj_set_style_bg_color(lightBtn, lv_color_hex(0x2196F3), 0);
lv_obj_set_style_radius(lightBtn, 12, 0); // 圆角
lv_obj_set_style_shadow_width(lightBtn, 10, 0); // 阴影
// 按钮文字
lv_obj_t* btnLabel = lv_label_create(lightBtn);
lv_label_set_text(btnLabel, "开灯");
lv_obj_center(btnLabel);
// 点击事件
lv_obj_add_event_cb(lightBtn, light_btn_cb, LV_EVENT_CLICKED, NULL);
// 事件回调
void light_btn_cb(lv_event_t* e) {
Serial.println("灯光按钮点击");
// 发布 MQTT 消息
}

用于开/关状态控制:

lv_obj_t* switchCtrl = lv_switch_create(screen);
lv_obj_set_pos(switchCtrl, 200, 100);
lv_obj_set_size(switchCtrl, 60, 30);
// 开关样式(开启状态)
lv_obj_set_style_bg_color(switchCtrl, lv_palette_main(LV_PALETTE_GREEN),
LV_STATE_CHECKED);
// 开关样式(关闭状态)
lv_obj_set_style_bg_color(switchCtrl, lv_color_hex(0x555555), 0);
// 值变化事件
lv_obj_add_event_cb(switchCtrl, switch_cb, LV_EVENT_VALUE_CHANGED, NULL);
void switch_cb(lv_event_t* e) {
lv_obj_t* sw = lv_event_get_target(e);
bool isOn = lv_obj_has_state(sw, LV_STATE_CHECKED);
Serial.printf("开关: %s\n", isOn ? "ON" : "OFF");
}

用于调节数值(如灯光亮度、温度设定):

lv_obj_t* slider = lv_slider_create(screen);
lv_obj_set_pos(slider, 40, 200);
lv_obj_set_size(slider, 200, 20);
// 设置范围
lv_slider_set_range(slider, 0, 100);
lv_slider_set_value(slider, 50, LV_ANIM_ON);
// 数值显示标签
lv_obj_t* sliderValue = lv_label_create(screen);
lv_label_set_text_fmt(sliderValue, "%d%%", 50);
lv_obj_align_to(sliderValue, slider, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
// 值变化事件
lv_obj_add_event_cb(slider, slider_cb, LV_EVENT_VALUE_CHANGED, sliderValue);
void slider_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);
int value = lv_slider_get_value(slider);
lv_label_set_text_fmt(label, "%d%%", value);
}
// 创建容器
lv_obj_t* container = lv_obj_create(screen);
lv_obj_set_size(container, 280, 200);
lv_obj_center(container);
// 启用 Flex 布局
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_flex_align(container,
LV_FLEX_ALIGN_SPACE_EVENLY, // 主轴
LV_FLEX_ALIGN_CENTER, // 交叉轴
LV_FLEX_ALIGN_SPACE_EVENLY); // 交叉轴(多行)
// 在容器中添加子控件(自动排列)
for (int i = 0; i < 6; i++) {
lv_obj_t* btn = lv_btn_create(container);
lv_obj_set_size(btn, 70, 50);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text_fmt(label, "Btn %d", i + 1);
lv_obj_center(label);
}
// 创建网格容器
lv_obj_t* grid = lv_obj_create(screen);
lv_obj_set_size(grid, 280, 240);
lv_obj_center(grid);
// 定义网格列和行
static lv_coord_t col_dsc[] = {80, 80, 80, LV_GRID_TEMPLATE_LAST};
static lv_coord_t row_dsc[] = {50, 50, 50, 50, LV_GRID_TEMPLATE_LAST};
lv_obj_set_grid_dsc_array(grid, col_dsc, row_dsc);
// 在网格中放置控件
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
lv_obj_t* btn = lv_btn_create(grid);
lv_obj_set_grid_cell(btn, LV_GRID_ALIGN_STRETCH, col, 1,
LV_GRID_ALIGN_STRETCH, row, 1);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text_fmt(label, "R%dC%d", row, col);
lv_obj_center(label);
}
}
// 创建样式
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0x1E88E5));
lv_style_set_bg_grad_color(&style, lv_color_hex(0x1565C0)); // 渐变色
lv_style_set_bg_grad_dir(&style, LV_GRAD_DIR_VER); // 垂直渐变
lv_style_set_radius(&style, 16); // 圆角
lv_style_set_border_width(&style, 2);
lv_style_set_border_color(&style, lv_color_hex(0x0D47A1));
lv_style_set_shadow_width(&style, 8);
lv_style_set_shadow_color(&style, lv_color_hex(0x000000));
lv_style_set_shadow_ofs_x(&style, 2);
lv_style_set_shadow_ofs_y(&style, 4);
// 应用样式到控件
lv_obj_add_style(btn, &style, 0); // 默认状态
lv_obj_add_style(btn, &style_pressed, LV_STATE_PRESSED); // 按下状态
// 淡入动画
void fade_in(lv_obj_t* obj) {
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_style_opa);
lv_anim_set_values(&a, LV_OPA_TRANSP, LV_OPA_COVER);
lv_anim_set_time(&a, 300); // 300ms
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
lv_anim_start(&a);
}
// 位移动画
void slide_in(lv_obj_t* obj) {
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_set_values(&a, -200, 0); // 从左侧滑入
lv_anim_set_time(&a, 400);
lv_anim_set_path_cb(&a, lv_anim_path_bounce);
lv_anim_start(&a);
}
// 页面切换动画
void page_transition(lv_obj_t* oldPage, lv_obj_t* newPage) {
// 旧页淡出
lv_anim_t a_fade_out;
lv_anim_init(&a_fade_out);
lv_anim_set_var(&a_fade_out, oldPage);
lv_anim_set_exec_cb(&a_fade_out, (lv_anim_exec_xcb_t)lv_obj_set_style_opa);
lv_anim_set_values(&a_fade_out, LV_OPA_COVER, LV_OPA_TRANSP);
lv_anim_set_time(&a_fade_out, 200);
lv_anim_start(&a_fade_out);
// 新页滑入
lv_anim_t a_slide_in;
lv_anim_init(&a_slide_in);
lv_anim_set_var(&a_slide_in, newPage);
lv_anim_set_exec_cb(&a_slide_in, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_set_values(&a_slide_in, 320, 0);
lv_anim_set_time(&a_slide_in, 300);
lv_anim_set_path_cb(&a_slide_in, lv_anim_path_ease_out);
lv_anim_start(&a_slide_in);
}
// 创建主仪表板
void create_main_dashboard() {
lv_obj_t* screen = lv_scr_act();
lv_obj_set_style_bg_color(screen, lv_color_hex(0x1a1a2e), 0);
// 状态标题栏
lv_obj_t* title = lv_label_create(screen);
lv_label_set_text(title, "🏠 智能家居");
lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
// 温度显示
lv_obj_t* temp_card = lv_obj_create(screen);
lv_obj_set_size(temp_card, 130, 100);
lv_obj_set_pos(temp_card, 10, 60);
lv_obj_set_style_bg_color(temp_card, lv_color_hex(0x16213e), 0);
lv_obj_set_style_radius(temp_card, 12, 0);
lv_obj_t* temp_icon = lv_label_create(temp_card);
lv_label_set_text(temp_icon, "🌡️");
lv_obj_align(temp_icon, LV_ALIGN_TOP_LEFT, 10, 5);
lv_obj_t* temp_value = lv_label_create(temp_card);
lv_label_set_text(temp_value, "24.5°C");
lv_obj_set_style_text_font(temp_value, &lv_font_montserrat_24, 0);
lv_obj_set_style_text_color(temp_value, lv_color_hex(0x00E5FF), 0);
lv_obj_align(temp_value, LV_ALIGN_BOTTOM_MID, 0, -10);
// 湿度卡片
lv_obj_t* hum_card = lv_obj_create(screen);
lv_obj_set_size(hum_card, 130, 100);
lv_obj_set_pos(hum_card, 150, 60);
lv_obj_set_style_bg_color(hum_card, lv_color_hex(0x16213e), 0);
lv_obj_set_style_radius(hum_card, 12, 0);
lv_obj_t* hum_icon = lv_label_create(hum_card);
lv_label_set_text(hum_icon, "💧");
lv_obj_align(hum_icon, LV_ALIGN_TOP_LEFT, 10, 5);
lv_obj_t* hum_value = lv_label_create(hum_card);
lv_label_set_text(hum_value, "62%");
lv_obj_set_style_text_font(hum_value, &lv_font_montserrat_24, 0);
lv_obj_set_style_text_color(hum_value, lv_color_hex(0x76FF03), 0);
lv_obj_align(hum_value, LV_ALIGN_BOTTOM_MID, 0, -10);
// 灯光控制
lv_obj_t* light_switch = lv_switch_create(screen);
lv_obj_set_pos(light_switch, 30, 190);
lv_obj_t* light_label = lv_label_create(screen);
lv_label_set_text(light_label, "💡 客厅灯");
lv_obj_align_to(light_label, light_switch, LV_ALIGN_OUT_LEFT_MID, -10, 0);
// 场景按钮
create_scene_button(screen, "🌙 睡眠", 10, 240, LV_PALETTE_INDIGO);
create_scene_button(screen, "🏃 离家", 110, 240, LV_PALETTE_TEAL);
create_scene_button(screen, "🏠 回家", 210, 240, LV_PALETTE_GREEN);
}
void create_scene_button(lv_obj_t* parent, const char* text,
int x, int y, lv_palette_t color) {
lv_obj_t* btn = lv_btn_create(parent);
lv_obj_set_size(btn, 90, 45);
lv_obj_set_pos(btn, x, y);
lv_obj_set_style_bg_color(btn, lv_palette_main(color), 0);
lv_obj_set_style_radius(btn, 22, 0);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, text);
lv_obj_center(label);
}

原因: 控件尺寸或位置超出了屏幕边界

解决方案: 使用 lv_obj_align()lv_obj_align_to() 代替绝对位置

原因: 样式设置了错误的部件或状态

解决方案:

// 正确:添加到控件主体(part = 0)
lv_obj_add_style(btn, &style, 0);
// 或者指定部件
lv_obj_add_style(slider, &style_track, LV_PART_INDICATOR);

本节介绍了 LVGL UI 设计基础:

  1. 核心控件:Label、Button、Switch、Slider 等智能家居常用控件
  2. 布局管理:Flex 弹性布局和 Grid 网格布局
  3. 样式系统:颜色、圆角、渐变、阴影、边框
  4. 动画系统:位移动画、淡入淡出、页面切换过渡
  5. 仪表板设计:温度卡片、灯光控制、场景按钮的布局