LVGL组件设计之模拟桌面

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 依赖组件

2.3 源码路径

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. 注意事项

  1. 内存管理

    • 确保正确释放分配的内存
    • 检查内存分配失败的情况
  2. 图标资源

    • 确保所有图标资源都已正确加载
    • 图标大小应适配状态栏高度
  3. 性能优化

    • 避免频繁更新状态栏图标
    • 合理使用LVGL的缓存机制

7. 未来优化方向

  1. 布局适配

    • 考虑不同屏幕分辨率的适配
    • 使用百分比布局确保界面自适应
  2. 界面美化

    • 添加过渡动画效果
    • 优化图标设计
  3. 功能扩展

    • 支持更多状态显示
    • 添加快速设置面板
    • 实现桌面小部件
相关推荐
嵌入式-老费5 分钟前
Linux上位机开发实战(x86和arm自由切换)
linux·运维·arm开发
猪猪侠|ZZXia9 分钟前
# linux有哪些显示服务器协议、显示服务器、显示管理器、窗口管理器?有哪些用于开发图形用户界面的工具包?有哪些桌面环境?
linux·服务器
网络安全(king)12 分钟前
基于java社交网络安全的知识图谱的构建与实现
开发语言·网络·深度学习·安全·web安全·php
艾希逐月20 分钟前
【动手实验】TCP 连接的建立与关闭抓包分析
网络·tcp/ip
人间凡尔赛23 分钟前
VSCode-Server 在 Linux 容器中的手动安装指南
linux·运维·服务器·docker
Chenyu_31025 分钟前
05.基于 TCP 的远程计算器:从协议设计到高并发实现
linux·网络·c++·vscode·网络协议·tcp/ip·算法
光芒Shine38 分钟前
【物联网-以太网-W5500】
物联网
板鸭〈小号〉1 小时前
Linux开发工具----vim
linux·运维·vim
衡玖1 小时前
c语言闯算法--排序
c语言·数据结构·算法
洛神灬殇1 小时前
【技术白皮书】内功心法 | 第二部分 | Telnet远程登录的工作原理
运维·服务器·网络