LVGL组件设计之模拟桌面
1. 概述
模拟桌面组件(desktop)是基于LVGL实现的一个通用桌面管理组件,为智能家居系统提供类似手机桌面的用户界面。该组件封装了桌面布局、状态栏显示和主页面功能,并提供了简单的接口用于应用程序启动和状态显示等操作。项目详情请参考《AM335x Linux平台LVGL演示项目源码说明》
2. 开发环境说明
2.1 开发环境
- 处理器:AM3354
- 显示屏:支持触摸功能的LCD屏幕
- 操作系统:Linux 3.2
- LVGL版本:v8.3
- 构建工具:CMake 3.16+
- 交叉编译工具链:arm-arago-linux-gnueabi-gcc 4.5.3
2.2 依赖组件
- LVGL核心库
- 触摸屏驱动(tslib)
- 状态栏组件(status_bar)
- 虚拟键盘组件(virt_kb)
- 用户管理(usermgr)
2.3 源码路径
- 桌面管理模块:
/com/desktop/
- 状态栏组件:
/ui/bar/status_bar/
- 主页面:
/pages/home_page/
- 项目源码:https://gitcode.com/am335xt3/lvgl/tree/main/apps/smarthome/src/com/desktop
3. 核心功能
3.1 组件结构
桌面组件主要由以下部分组成:
c
typedef struct {
lv_obj_t *obj; // 桌面对象
lv_obj_t *status_bar; // 状态栏
lv_obj_t *content; // 内容区域
lv_obj_t *home_page; // 主页面
} desktop_t;
状态栏组件结构:
c
typedef struct {
lv_obj_t *obj; // 状态栏对象
lv_obj_t *wifi_icon; // WiFi图标
lv_obj_t *battery_icon; // 电池图标
lv_obj_t *notify_icon; // 通知图标
} status_bar_t;
3.2 主要接口
组件提供以下核心接口:
Desktop接口
desktop_create(lv_obj_t *parent)
: 创建桌面组件desktop_add_app(desktop_t *desktop, const char *title, const char *icon_path, int app_id)
: 添加APP图标到桌面desktop_add_page(desktop_t *desktop, lv_obj_t *page, int app_id)
: 添加页面到桌面desktop_set_page_layout(desktop_t *desktop, lv_obj_t *page, lv_coord_t x, lv_coord_t y, lv_coord_t width, lv_coord_t height)
: 设置页面布局desktop_close_all_pages(desktop_t *desktop)
: 关闭所有页面desktop_register_create_page_cb(desktop_t *desktop, create_page_cb_t cb)
: 注册创建页面的回调函数
Status Bar接口
status_bar_create(lv_obj_t *parent)
: 创建状态栏status_bar_set_wifi_state(status_bar_t *status_bar, bool connected)
: 设置WiFi状态status_bar_set_battery_state(status_bar_t *status_bar, uint8_t level)
: 设置电池状态status_bar_set_notify_state(status_bar_t *status_bar, bool has_notify)
: 设置通知状态
4. 实现细节
4.1 模拟桌面实现
4.1.1 桌面布局实现
桌面组件采用垂直布局,从上到下依次是状态栏和内容区域。状态栏固定高度,内容区域自适应剩余空间。
关键代码:
c
// 全局桌面实例指针定义
desktop_t *g_desktop = NULL;
desktop_t *desktop_create(lv_obj_t *parent) {
// 分配内存
desktop_t *desktop = lv_mem_alloc(sizeof(desktop_t));
if (desktop == NULL) {
return NULL;
}
lv_memset(desktop, 0, sizeof(desktop_t));
// 创建桌面对象
desktop->obj = lv_obj_create(parent);
if (desktop->obj == NULL) {
lv_mem_free(desktop);
return NULL;
}
// 设置桌面样式
lv_obj_set_size(desktop->obj, LV_PCT(100), LV_PCT(100));
lv_obj_clear_flag(desktop->obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_pad_all(desktop->obj, 0, 0);
lv_obj_set_style_border_width(desktop->obj, 0, 0);
// 设置背景图片
lv_obj_set_style_bg_img_src(desktop->obj, DESKTOP_BG_PATH, 0);
lv_obj_set_style_bg_img_opa(desktop->obj, LV_OPA_COVER, 0);
// 创建状态栏
desktop->status_bar = status_bar_create(desktop->obj);
// 创建应用按钮容器
desktop->app_btn_container = lv_obj_create(desktop->obj);
lv_obj_set_size(desktop->app_btn_container, LV_PCT(100), LV_PCT(90));
lv_obj_align(desktop->app_btn_container, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_opa(desktop->app_btn_container, LV_OPA_0, 0);
lv_obj_clear_flag(desktop->app_btn_container, LV_OBJ_FLAG_SCROLLABLE);
// 创建页面容器
desktop->page_container = lv_obj_create(desktop->obj);
lv_obj_set_size(desktop->page_container, LV_PCT(100), LV_PCT(100));
lv_obj_align(desktop->page_container, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_bg_opa(desktop->page_container, LV_OPA_0, 0);
lv_obj_set_style_border_width(desktop->page_container, 0, 0);
lv_obj_set_style_pad_all(desktop->page_container, 0, 0);
lv_obj_clear_flag(desktop->page_container, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(desktop->page_container, LV_OBJ_FLAG_HIDDEN);
// 初始化计数器
desktop->app_count = 0;
desktop->page_count = 0;
return desktop;
}
4.1.2 添加应用组件实现
c
void desktop_add_app(desktop_t *desktop, const char *title, const char *icon_path,int app_id) {
if (desktop == NULL || desktop->app_count >= MAX_APP_COUNT) {
return;
}
// 保存页面到pages数组
desktop->pages[desktop->page_count].page = NULL;
desktop->pages[desktop->page_count].app_id = app_id;
// 创建应用按钮
desktop_btn_t *btn = desktop_btn_create(desktop->app_btn_container, title,
icon_path ? icon_path : DESKTOP_ICON_DIR"/default.png");
if (btn == NULL) {
return;
}
// 计算并设置按钮位置
lv_coord_t x = (desktop->app_count % 6) * 68 + 20;
lv_coord_t y = (desktop->app_count / 6) * 84 + 20;
desktop_btn_set_pos(btn, x, y);
// 注册按钮事件回调
desktop_btn_add_event_cb(btn, app_btn_event_cb, desktop);
// 保存按钮对象
desktop->apps_btn[desktop->app_count] = btn;
// 更新计数器
desktop->app_count++;
desktop->page_count++;
}
void desktop_add_page(desktop_t *desktop, lv_obj_t *page,int app_id)
{
if (desktop == NULL || page == NULL) {
return;
}
// 将页面的父对象设置为页面容器
lv_obj_set_parent(page, desktop->page_container);
lv_obj_add_flag(page, LV_OBJ_FLAG_HIDDEN);
int idx = -1;
int i = 0;
// 查找页面在pages数组中的索引
for (i = 0; i < MAX_APP_COUNT; i++) {
if (desktop->pages[i].app_id == app_id) {
idx = i;
break;
}
}
// 如果页面不在pages数组中,则返回
if (idx == -1) {
return;
}
// 更新pages数组
desktop->pages[idx].page = page;
}
4.1.2 应用组件打开和关闭实现
应用组件的打开和关闭通过按钮点击事件实现。当用户点击应用按钮时,首先隐藏所有页面,然后显示被点击的页面。
c
static void app_btn_event_cb(lv_event_t *e) {
lv_obj_t *btn = lv_event_get_target(e);
desktop_t *desktop = lv_event_get_user_data(e);
uint8_t i = 0;
// 查找按钮在apps_btn数组中的索引
int idx = -1;
for (i = 0; i < MAX_APP_COUNT; i++) {
if(desktop_btn_recognize_btn(desktop->apps_btn[i], btn)) {
idx = i;
break;
}
}
// 如果按钮不在apps_btn数组中,则返回
if (idx == -1) {
LV_LOG_ERROR("按钮不存在");
return;
}
if(!desktop->pages[idx].page) {
if (desktop->create_page_cb) {
desktop->create_page_cb(desktop->page_container, desktop->pages[idx].app_id);
} else {
LV_LOG_ERROR("未注册创建页面回调函数");
return;
}
}
// 隐藏所有页面
for(i = 0; i < desktop->page_count; i++) {
if(desktop->pages[i].page) {
lv_obj_add_flag(desktop->pages[i].page, LV_OBJ_FLAG_HIDDEN);
}
}
if(desktop->pages[idx].page) {
LV_LOG_USER("display app_id:%d", desktop->pages[idx].app_id);
// 显示被点击的页面
lv_obj_clear_flag(desktop->pages[idx].page, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(desktop->page_container, LV_OBJ_FLAG_HIDDEN);
} else {
LV_LOG_ERROR("页面不存在,请先注册页面,app_id:%d", desktop->pages[idx].app_id);
}
}
void desktop_close_all_pages(desktop_t *desktop) {
if (desktop == NULL) {
return;
}
LV_LOG_USER("Desktop: Closing all pages\n");
// 隐藏所有页面
uint8_t i = 0;
for(i = 0; i < desktop->page_count; i++) {
if(desktop->pages[i].page) {
lv_obj_add_flag(desktop->pages[i].page, LV_OBJ_FLAG_HIDDEN);
}
}
// 隐藏页面容器
lv_obj_add_flag(desktop->page_container, LV_OBJ_FLAG_HIDDEN);
}
4.2 状态栏实现
4.2.1 状态栏布局实现
状态栏组件采用水平布局,从左到右依次显示WiFi状态、电池电量和通知图标。
关键代码:
c
status_bar_t *status_bar_create(lv_obj_t *parent) {
status_bar_t *status_bar = lv_mem_alloc(sizeof(status_bar_t));
if (status_bar == NULL) {
LV_LOG_ERROR("Failed to allocate memory for status bar");
return NULL;
}
// 创建状态栏容器
status_bar->obj = lv_obj_create(parent);
lv_obj_set_size(status_bar->obj, LV_PCT(100), STATUS_BAR_HEIGHT);
lv_obj_set_flex_flow(status_bar->obj, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(status_bar->obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
// 创建WiFi图标
status_bar->wifi_icon = lv_img_create(status_bar->obj);
lv_img_set_src(status_bar->wifi_icon, "A:/wifi.png");
// 创建电池图标
status_bar->battery_icon = lv_img_create(status_bar->obj);
lv_img_set_src(status_bar->battery_icon, "A:/battery.png");
// 创建通知图标
status_bar->notify_icon = lv_img_create(status_bar->obj);
lv_img_set_src(status_bar->notify_icon, "A:/notify.png");
return status_bar;
}
4.2.2 状态更新接口
状态栏提供了更新各个图标状态的接口:
c
void status_bar_set_wifi_state(status_bar_t *status_bar, bool connected) {
if (status_bar == NULL) return;
// 根据连接状态更新WiFi图标
if (connected) {
lv_img_set_src(status_bar->wifi_icon, "A:/wifi_connected.png");
} else {
lv_img_set_src(status_bar->wifi_icon, "A:/wifi_disconnected.png");
}
}
void status_bar_set_battery_state(status_bar_t *status_bar, uint8_t level) {
if (status_bar == NULL) return;
// 根据电量级别更新电池图标
if (level > 80) {
lv_img_set_src(status_bar->battery_icon, "A:/battery_full.png");
} else if (level > 20) {
lv_img_set_src(status_bar->battery_icon, "A:/battery_medium.png");
} else {
lv_img_set_src(status_bar->battery_icon, "A:/battery_low.png");
}
}
5. 使用示例

5.1 创建桌面
c
// 创建桌面组件
desktop_t *desktop = desktop_create(lv_scr_act());
if (desktop == NULL) {
LV_LOG_ERROR("Failed to create desktop");
return;
}
5.2 应用组件定义
c
/**
* @brief 应用页面ID枚举
*/
typedef enum {
APP_PAGE_ID_CTRL, /**< 控制页面ID */
APP_PAGE_ID_VIDEO, /**< 视频页面ID */
APP_PAGE_ID_USER_MGR, /**< 用户管理页面ID */
MAX_APP_PAGE_COUNT
} app_page_id_t;
/**
* @brief 应用页面信息结构体
*/
typedef struct {
lv_obj_t *obj; /**< 页面对象 */
app_page_id_t app_id; /**< 应用页面ID */
const char *title; /**< 页面标题 */
const char *icon_path; /**< 页面图标 */
ctrl_page_create_t page_create_cb; /**< 创建页面回调函数 */
} app_page_info_t;
5.3 应用组件配置
c
/*
* 子页面信息
* app_id: 应用ID
* title: 页面标题
* icon_path: 图标路径
* page_create_cb: 创建页面的回调函数
*/
static app_page_info_t app_pages_info[MAX_APP_PAGE_COUNT] = {
{NULL, APP_PAGE_ID_CTRL, "ctrl", DESKTOP_ICON_DIR"/ctrl_page.png",ctrl_page_create},
{NULL, APP_PAGE_ID_VIDEO, "video", DESKTOP_ICON_DIR"/video_page.png",video_page_create},
{NULL, APP_PAGE_ID_USER_MGR, "user", DESKTOP_ICON_DIR"/user_mgr_page.png",user_mgr_page_create},
};
5.4 添加应用组件图标
c
static void home_sub_page_closed_cb(void)
{
if (g_home_page == NULL)
return;
desktop_close_all_pages(g_home_page->desktop);
}
static void home_create_sub_page(lv_obj_t *parent, int app_id)
{
if (g_home_page == NULL) {
LV_LOG_ERROR("g_home_page is NULL");
return;
}
if (app_id < 0 || app_id >= MAX_APP_PAGE_COUNT) {
LV_LOG_ERROR("Invalid app_id: %d", app_id);
return;
}
app_page_info_t *page_info = &app_pages_info[app_id];
if (page_info->obj != NULL) {
LV_LOG_ERROR("Page already exists for app_id: %d", app_id);
return;
}
// 创建子页面
LV_LOG_USER("create sub page for app_id: %d", app_id);
page_info->obj = page_info->page_create_cb(parent, home_sub_page_closed_cb);
if (page_info->obj == NULL) {
LV_LOG_ERROR("Failed to create page for app_id: %d", app_id);
return;
}
// 将子页面添加到桌面
desktop_add_page(g_home_page->desktop, page_info->obj, app_id);
}
home_page_t *create_home_screen(lv_obj_t * parent) {
// 分配主页面结构体内存
home_page_t *home_page = (home_page_t *)lv_mem_alloc(sizeof(home_page_t));
if (home_page == NULL) {
return NULL;
}
lv_memset(home_page, 0, sizeof(home_page_t));
// 将主页面结构体指针赋值给全局变量
g_home_page = home_page;
// 初始化结构体成员
home_page->parent = parent;
// 创建桌面组件
home_page->desktop = desktop_create(parent);
if (home_page->desktop == NULL) {
lv_mem_free(home_page);
return NULL;
}
desktop_register_create_page_cb(home_page->desktop,home_create_sub_page);
// 添加标题和图标到模拟桌面
int i = 0;
for (i = 0; i < MAX_APP_PAGE_COUNT; i++) {
app_page_info_t *page_info = &app_pages_info[i];
desktop_add_app(home_page->desktop, page_info->title, page_info->icon_path, page_info->app_id);
}
return home_page;
}
5.5 更新状态栏
c
// 获取状态栏对象并更新状态
status_bar_t *status_bar = (status_bar_t *)desktop_get_status_bar(desktop);
status_bar_set_wifi_state(status_bar, true);
status_bar_set_battery_state(status_bar, 90);
6. 注意事项
-
内存管理
- 确保正确释放分配的内存
- 检查内存分配失败的情况
-
图标资源
- 确保所有图标资源都已正确加载
- 图标大小应适配状态栏高度
-
性能优化
- 避免频繁更新状态栏图标
- 合理使用LVGL的缓存机制
7. 未来优化方向
-
布局适配
- 考虑不同屏幕分辨率的适配
- 使用百分比布局确保界面自适应
-
界面美化
- 添加过渡动画效果
- 优化图标设计
-
功能扩展
- 支持更多状态显示
- 添加快速设置面板
- 实现桌面小部件