开源一个基于LVGL的图层管理框架(LMF)

目录

概要

移植

1、文件移植

2、接口调用

(1)lmf_init

(2)lmf_menu_update_task

接口

(1)lmf_menu_manage_register

(2)lmf_menu_enter

(3)lmf_menu_exit

(4)lmf_menu_update

(5)图层和界面扩展

小结

完整代码

(1)lvgl_lmf.c

(2)lvgl_lmf.h

概要

一个基于LVGL的图层管理框架,内部通过单向链表实现,支持多个图层界面的叠加显示,支持图层里多个界面的入栈和出栈。支持传递更新标志位和更新数据到每个图层的顶层界面。相关代码已在GitHub 开源,链接如下:GitHub - murongbaibai/lvgl_lmf: 基于LVGL的图层管理框架(LMF) · GitHub

也可以在文章末尾复制完整代码。下面介绍代码的移植和使用。

移植

1、文件移植

仅需将lvgl_lmf.c、lvgl_lmf.h文件下载下来,加入工程中即可。

2、接口调用

需要调用的接口有两个,lmf_init和lmf_menu_update_task。

(1)lmf_init

图层管理框架(LMF)的初始化接口,在LVGL初始化完成之后调用即可。

cpp 复制代码
    // 初始化lvgl
    lvgl_port_cfg_t lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
    lvgl_port_cfg.task_stack = 20480;
    lvgl_port_init(&lvgl_port_cfg);
    lv_display_t* disp = rgb_lcd_addto_lvgl();
    touch_addto_lvgl(disp);

    lvgl_port_lock(0);
    // 初始化图层界面框架
    lmf_init();
    lvgl_port_unlock();

注意:如果的多任务、多线程环境下,且lvgl更新任务在其他任务中执行,需要实现 lvgl_port_lock和lvgl_port_unlock这两个接口。

cpp 复制代码
/**
 * @brief LVGL互斥锁加锁函数
 * @param timeout_ms 超时时间,单位毫秒,0表示无限等待
 * @note 该函数为弱函数,使用esp_lvgl_port库时会被esp_lvgl_port库中的函数覆盖,其他情况用户可以在其他文件中实现该函数
 */
__attribute__((weak)) bool lvgl_port_lock(uint32_t timeout_ms) { return true; }
/**
 * @brief LVGL互斥锁解锁函数
 * @note 该函数为弱函数,使用esp_lvgl_port库时会被esp_lvgl_port库中的函数覆盖,其他情况用户可以在其他文件中实现该函数
 */
__attribute__((weak)) void lvgl_port_unlock(void) {}

用于保证和lvgl更新任务互斥执行。

(2)lmf_menu_update_task

图层管理框架(LMF)的界面更新任务,在lmf_init初始化完成之后轮询调用,或者在任务、线程中轮询调用。freeRTOS环境的的示例代码如下。

cpp 复制代码
static void menu_update_task(void *arg)
{
    while(true)
    {
        lmf_menu_update_task();
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

void lmf_init_complete_callback(void)
{
    // 初始化其他界面组件
    lv_home_menu_init();

    // 开启界面更新任务
    xTaskCreate(menu_update_task, "menu_update_task", 20480, NULL, 6, NULL);
}

lmf_init_complete_callback为lmf_init初始化完成回调,可以在此注册界面和开启界面更新任务。

cpp 复制代码
/**
 * @brief 界面初始化完成回调
 * @note 该函数为弱函数,用户可以在其他文件中实现该函数
 */
__attribute__((weak)) void lmf_init_complete_callback(void) {}

接口

移植完成之后需要使用lmf_menu_manage_register注册自己的界面,注册界面后使用lmf_menu_enter、lmf_menu_exit在指定图层进入、退出界面,使用lmf_menu_update向指定的界面传递更新参数。

(1)lmf_menu_manage_register

注册一个界面管理器,传入lmf_menu_manage_t结构体,里面有界面编号(枚举类型)以及界面的三个操作函数menu_create_fun、menu_delete_fun、menu_update_fun,分别是界面创建函数、界面删除函数、界面更新函数。

cpp 复制代码
// 界面管理器
typedef struct lmf_menu_manage_t
{
    enum lmf_menu_t menu;
    void (*menu_create_fun)(lv_obj_t *layer);
    void (*menu_delete_fun)(lv_obj_t *layer);
    void (*menu_update_fun)(lv_obj_t *layer, enum lmf_menu_update_flag_t flag, void *arg);
    struct lmf_menu_manage_t *next;
}lmf_menu_manage_t;


// 示例
static void lv_home_menu_create(lv_obj_t *layer)
{
    lv_obj_t *label = lv_label_create(layer);
    lv_obj_center(label);
    lv_label_set_text(label, "hello world");
}

static void lv_home_menu_delete(lv_obj_t *layer)
{
    lv_obj_clean(layer);
}

static void lv_home_menu_update(lv_obj_t *layer, enum lmf_menu_update_flag_t flag, void *arg)
{
    switch(flag)
    {
        // 更新标签内容
        case LMF_MENU_UPDATE_FLAG0:
        {
            lv_obj_t *label = lv_obj_get_child(layer, 0);
            lv_label_set_text(label, (const char *)arg);
        }
        break;
        default:
        {
            
        }
        break;
    }
}

void lv_home_menu_init(void)
{
    static const lmf_menu_manage_t menu_manage = {
        .menu = LMF_MENU_HOME,
        .menu_create_fun = lv_home_menu_create,
        .menu_delete_fun = lv_home_menu_delete,
        .menu_update_fun = lv_home_menu_update,
        .next = NULL,
    };
    lmf_menu_manage_register(&menu_manage);
}
(2)lmf_menu_enter

指定一个图层进入界面,传入图层编号和界面编号。调用后,内部会在该图层调用界面的menu_create_fun 函数,最后会在该图层显示指定界面。

cpp 复制代码
/**
 * @brief 在指定图层进入新界面
 * @param layer 图层
 * @param menu 界面类型
 * @note 该函数不能和LVGL任务同时使用,需要加互斥锁
 */
void lmf_menu_enter(enum lmf_layer_t layer, enum lmf_menu_t menu)

// 示例
lmf_menu_enter(LMF_LAYER_0, LMF_MENU_HOME);

注意:可以在一个图层多次调用lmf_menu_enter,调用后会调用menu_delete_fun ,删除该图层当前显示的界面并显示指定的界面,同时会把当前显示的界面保存到链表栈中,使用lmf_menu_exit回到上一次界面。

(3)lmf_menu_exit

指定一个图层退出界面,传入图层编号,调用后,内部会调用该图层当前显示的界面的menu_delete_fun ,删除当前显示的界面并回到上一界面,调用上一界面的menu_create_fun进行界面显示。

cpp 复制代码
/**
 * @brief 退出图层当前界面到上一层界面
 * @param layer 图层
 * @note 该函数不能和LVGL任务同时使用,需要加互斥锁
 */
void lmf_menu_exit(enum lmf_layer_t layer)

// 示例
lmf_menu_exit(LMF_LAYER_0);

注意:如果图层没有界面了,会什么都不显示。

(4)lmf_menu_update

传递给指定界面更新标志位及更新参数。调用后,内部会记录更新标志位和更新参数。在lmf_menu_update_task任务中会轮询每个图层的当前显示界面,如果是指定的界面就进行更新标志位和更新参数的传递,用户在menu_update_fun接收并处理更新标志位和更新参数,即可实现界面的异步更新。

cpp 复制代码
/**
 * @brief 传递界面更新标志位
 * @param menu 界面类型
 * @param update_flag 更新标志位
 * @param arg 更新参数
 */
void lmf_menu_update(enum lmf_menu_t menu, enum lmf_menu_update_flag_t flag, void *arg)

// 示例
static const char *str = "hello lmf";
lmf_menu_update(LMF_MENU_HOME, LMF_MENU_UPDATE_FLAG0, (void *)str);

注意:更新标志位只有32位,对应LMF_MENU_UPDATE_FLAG0~31超出不处理。

(5)图层和界面扩展

在lvgl_lmf.h中定义了图层和界面的枚举类型,修改enum lmf_layer_t可以自定义图层数量,图层编号越大,层级越高。修改lmf_menu_t 可以新加一个界面管理器。

cpp 复制代码
/**
 * @brief 图层命名
 * @note 由用户命名,支持无限扩展,取决于内存大小
 */
enum lmf_layer_t {
    LMF_LAYER_0 = 0,
    LMF_LAYER_1,
    LMF_LAYER_2,
    LMF_LAYER_3,
    LMF_LAYER_4,
    LMF_LAYER_MAX,
};

/**
 * @brief 界面命名
 * @note 由用户命名,支持无限扩展
 */
enum lmf_menu_t {
    LMF_MENU_NULL = 0,
    LMF_MENU_HOME,
    LMF_MENU_PLAY,
    LMF_MENU_PLAY_DEBUG,
    LMF_MENU_CTRL,
    LMF_MENU_DEVICE,
    LMF_MENU_DEVICE_PAIR,
    LMF_MENU_SET,
};

小结

实现了一个简易的LVGL图层管理框架,带有多图层,多界面处理和界面异步更新。下面是使用该框架的一个工程样板,供参考。

基于LVGL的LMF框架设计的界面

完整代码

(1)lvgl_lmf.c
cpp 复制代码
#include "app_main.h"

/** 
 * @brief 图层管理结构体
 */
typedef struct layer_manage_t
{
    enum lmf_menu_t menu;           // 当前图层的当前节点对应界面
    uint32_t update_flag;           // 当前图层的当前节点对应界面需要更新的标志位
    void *args[32];                 // 当前图层的当前节点对应界面的更新参数
    struct layer_manage_t *next;    // 当前图层的当前节点的下一节点
}layer_manage_t;

// 图层对象
static lv_obj_t*            layer_obj[LMF_LAYER_MAX];
// 界面管理器
static lmf_menu_manage_t*   menu_manage_head;
// 多图层管理器
static layer_manage_t*      layer_manage_head[LMF_LAYER_MAX];

/**
 * @brief 界面初始化完成回调
 * @note 该函数为弱函数,用户可以在其他文件中实现该函数
 */
__attribute__((weak)) void lmf_init_complete_callback(void) {}
/**
 * @brief LVGL互斥锁加锁函数
 * @param timeout_ms 超时时间,单位毫秒,0表示无限等待
 * @note 该函数为弱函数,使用esp_lvgl_port库时会被esp_lvgl_port库中的函数覆盖,其他情况用户可以在其他文件中实现该函数
 */
__attribute__((weak)) bool lvgl_port_lock(uint32_t timeout_ms) { return true; }
/**
 * @brief LVGL互斥锁解锁函数
 * @note 该函数为弱函数,使用esp_lvgl_port库时会被esp_lvgl_port库中的函数覆盖,其他情况用户可以在其他文件中实现该函数
 */
__attribute__((weak)) void lvgl_port_unlock(void) {}


/** 
 * @brief 注册界面管理器
 * @param menu_manage 界面管理器
 */
void lmf_menu_manage_register(const lmf_menu_manage_t *menu_manage)
{
    lmf_menu_manage_t *node = (lmf_menu_manage_t *)malloc(sizeof(lmf_menu_manage_t));
    memcpy(node, menu_manage, sizeof(lmf_menu_manage_t));
    node->next = menu_manage_head->next;
    menu_manage_head->next = node;
}

/**
 * @brief 获取界面管理器
 * @param menu 界面类型
 */
static lmf_menu_manage_t *menu_manage_get(enum lmf_menu_t menu)
{
    lmf_menu_manage_t *node = menu_manage_head->next;
    while(node != NULL)
    {
        if(node->menu == menu) break;
        node = node->next;
    }
    return node;
}

/**
 * @brief 界面更新任务
 */
void lmf_menu_update_task(void)
{
    if(lvgl_port_lock(0) == true)
    {
        // 只更新每个图层的最顶层的界面
        for(int i = 0; i < LMF_LAYER_MAX; i++)
        {
            if(layer_manage_head[i]->next != NULL)
            {
                lmf_menu_manage_t *menu_manage = menu_manage_get(layer_manage_head[i]->next->menu);
                if(menu_manage == NULL) continue;
                // 执行更新
                for(int flag = 0; flag < LMF_MENU_UPDATE_FLAG_MAX; flag++)
                {
                    if(layer_manage_head[i]->next->update_flag & (1<<flag))
                    {
                        // 清除更新标志位
                        layer_manage_head[i]->next->update_flag &= ~(1<<flag);
                        menu_manage->menu_update_fun(layer_obj[i], (enum lmf_menu_update_flag_t)flag, layer_manage_head[i]->next->args[flag]);
                    }
                }
            }
        }
        lvgl_port_unlock();
    }
}

/**
 * @brief 图层界面框架初始化
 */
void lmf_init(void)
{    
    lv_obj_t *screen = lv_screen_active();
    // 创建图层
    for(int i = 0; i < LMF_LAYER_MAX; i++)
    {
        layer_obj[i] = lv_obj_create(screen);
        lv_obj_set_style_bg_opa(layer_obj[i], LV_OPA_0, 0);
        lv_obj_set_style_border_width(layer_obj[i], 0, 0);
        lv_obj_set_style_pad_all(layer_obj[i], 0, 0);
        lv_obj_set_size(layer_obj[i], LV_PCT(100), LV_PCT(100));
        lv_obj_remove_flag(layer_obj[i], LV_OBJ_FLAG_SCROLLABLE);
        lv_obj_add_flag(layer_obj[i], LV_OBJ_FLAG_HIDDEN);
        lv_obj_set_user_data(layer_obj[i], (void *)i);
    }

    // 创建界面管理指针
    menu_manage_head = (lmf_menu_manage_t *)calloc(1, sizeof(lmf_menu_manage_t));
    // 创建图层管理指针
    for(int i = 0; i < LMF_LAYER_MAX; i++) layer_manage_head[i] = (layer_manage_t *)calloc(1, sizeof(layer_manage_t));

    // 初始化完成回调
    lmf_init_complete_callback();
}


/**
 * @brief 在指定图层进入新界面
 * @param layer 图层
 * @param menu 界面类型
 * @note 该函数不能和LVGL任务同时使用,需要加互斥锁
 */
void lmf_menu_enter(enum lmf_layer_t layer, enum lmf_menu_t menu)
{
    if(layer >= LMF_LAYER_MAX) return;

    // 如果是创建的第一个界面,取消图层的隐藏属性
    if(layer_manage_head[layer]->next == NULL) lv_obj_remove_flag(layer_obj[layer], LV_OBJ_FLAG_HIDDEN);
    // 如果图层已经创建有界面,需要叠加界面,清除上一个界面
    else
    {
        lmf_menu_manage_t *menu_manage = menu_manage_get(layer_manage_head[layer]->next->menu);
        if(menu_manage != NULL)
        {
            menu_manage->menu_delete_fun(layer_obj[layer]);
        }
    }
    // 头插界面节点
    layer_manage_t *new_node = (layer_manage_t*)malloc(sizeof(layer_manage_t));
    memset(new_node, 0, sizeof(layer_manage_t));
    new_node->menu = menu;
    new_node->next = layer_manage_head[layer]->next;
    layer_manage_head[layer]->next = new_node;

    // 显示新界面
    lmf_menu_manage_t *menu_manage = menu_manage_get(menu);
    if(menu_manage != NULL)
    {
        menu_manage->menu_create_fun(layer_obj[layer]);
    }
}

/**
 * @brief 退出图层当前界面到上一层界面
 * @param layer 图层
 * @note 该函数不能和LVGL任务同时使用,需要加互斥锁
 */
void lmf_menu_exit(enum lmf_layer_t layer)
{
    if(layer >= LMF_LAYER_MAX) return;
    if(layer_manage_head[layer]->next == NULL) return;

    // 清除当前界面
    lmf_menu_manage_t *menu_manage = menu_manage_get(layer_manage_head[layer]->next->menu);
    if(menu_manage != NULL)
    {
        menu_manage->menu_delete_fun(layer_obj[layer]);
    }

    // 清除界面节点
    layer_manage_t *delete_node = layer_manage_head[layer]->next;
    layer_manage_head[layer]->next = delete_node->next;
    free(delete_node);

    // 如果图层没有界面就隐藏
    if(layer_manage_head[layer]->next == NULL) lv_obj_add_flag(layer_obj[layer], LV_OBJ_FLAG_HIDDEN);
    // 如果图层有前一界面,就显示前一界面
    else 
    {   
        // 显示前一界面
        lmf_menu_manage_t *menu_manage = menu_manage_get(layer_manage_head[layer]->next->menu);
        if(menu_manage != NULL)
        {
            menu_manage->menu_create_fun(layer_obj[layer]);
        }
    }
}

/**
 * @brief 传递界面更新标志位
 * @param menu 界面类型
 * @param update_flag 更新标志位
 * @param arg 更新参数
 */
void lmf_menu_update(enum lmf_menu_t menu, enum lmf_menu_update_flag_t flag, void *arg)
{
    for(int i = 0; i < LMF_LAYER_MAX; i++)
    {
        if(layer_manage_head[i] == NULL) continue;
        if(layer_manage_head[i]->next != NULL)
        {
            if(layer_manage_head[i]->next->menu == menu)
            {
                if(flag < LMF_MENU_UPDATE_FLAG_MAX)
                {
                    layer_manage_head[i]->next->update_flag |= 1<<flag;
                    layer_manage_head[i]->next->args[flag] = arg;
                }
                else if(flag == LMF_MENU_UPDATE_FLAG_ALL)
                {
                    layer_manage_head[i]->next->update_flag = 0xFFFFFFFF;
                }
            }
        }
    }
}
(2)lvgl_lmf.h
cpp 复制代码
#ifndef LVGL_LMF_H
#define LVGL_LMF_H

#include "lvgl.h"

/**
 * @brief 界面命名
 * @note 由用户命名,支持无限扩展
 */
enum lmf_menu_t {
    LMF_MENU_NULL = 0,
    LMF_MENU_HOME,
    LMF_MENU_PLAY,
    LMF_MENU_PLAY_DEBUG,
    LMF_MENU_CTRL,
    LMF_MENU_DEVICE,
    LMF_MENU_DEVICE_PAIR,
    LMF_MENU_SET,
};

/**
 * @brief 界面更新标志位
 * @note 一个界面最多支持32个更新标志位,LVF_MENU_UPDATE_FLAG_ALL表示更新所有标志位
 */
enum lmf_menu_update_flag_t {
    LMF_MENU_UPDATE_FLAG0 = 0,
    LMF_MENU_UPDATE_FLAG1,
    LMF_MENU_UPDATE_FLAG2,
    LMF_MENU_UPDATE_FLAG3,
    LMF_MENU_UPDATE_FLAG4,
    LMF_MENU_UPDATE_FLAG5,
    LMF_MENU_UPDATE_FLAG6,
    LMF_MENU_UPDATE_FLAG7,
    LMF_MENU_UPDATE_FLAG8,
    LMF_MENU_UPDATE_FLAG9,
    LMF_MENU_UPDATE_FLAG10,
    LMF_MENU_UPDATE_FLAG11,
    LMF_MENU_UPDATE_FLAG12,
    LMF_MENU_UPDATE_FLAG13,
    LMF_MENU_UPDATE_FLAG14,
    LMF_MENU_UPDATE_FLAG15,
    LMF_MENU_UPDATE_FLAG16,
    LMF_MENU_UPDATE_FLAG17,
    LMF_MENU_UPDATE_FLAG18,
    LMF_MENU_UPDATE_FLAG19,
    LMF_MENU_UPDATE_FLAG20,
    LMF_MENU_UPDATE_FLAG21,
    LMF_MENU_UPDATE_FLAG22,
    LMF_MENU_UPDATE_FLAG23,
    LMF_MENU_UPDATE_FLAG24,
    LMF_MENU_UPDATE_FLAG25,
    LMF_MENU_UPDATE_FLAG26,
    LMF_MENU_UPDATE_FLAG27,
    LMF_MENU_UPDATE_FLAG28,
    LMF_MENU_UPDATE_FLAG29,
    LMF_MENU_UPDATE_FLAG30,
    LMF_MENU_UPDATE_FLAG31,
    LMF_MENU_UPDATE_FLAG_MAX,

    LMF_MENU_UPDATE_FLAG_ALL = LMF_MENU_UPDATE_FLAG_MAX,
};

/**
 * @brief 图层命名
 * @note 由用户命名,支持无限扩展,取决于内存大小
 */
enum lmf_layer_t {
    LMF_LAYER_0 = 0,
    LMF_LAYER_1,
    LMF_LAYER_2,
    LMF_LAYER_3,
    LMF_LAYER_4,
    LMF_LAYER_MAX,
};

// 界面管理器
typedef struct lmf_menu_manage_t
{
    enum lmf_menu_t menu;
    void (*menu_create_fun)(lv_obj_t *layer);
    void (*menu_delete_fun)(lv_obj_t *layer);
    void (*menu_update_fun)(lv_obj_t *layer, enum lmf_menu_update_flag_t flag, void *arg);
    struct lmf_menu_manage_t *next;
}lmf_menu_manage_t;


void lmf_init(void);
void lmf_menu_update_task(void);
void lmf_init_complete_callback(void);
void lmf_menu_manage_register(const lmf_menu_manage_t *menu_manage);
void lmf_menu_enter(enum lmf_layer_t layer, enum lmf_menu_t menu);
void lmf_menu_exit(enum lmf_layer_t layer);
void lmf_menu_update(enum lmf_menu_t menu, enum lmf_menu_update_flag_t flag, void *arg);

#endif
相关推荐
拾起_3691 小时前
04-V2 双循环与 V1 的核心差异
人工智能·开源
拾起_3691 小时前
05-Event Sourcing 事件系统
人工智能·开源
拾起_3692 小时前
03-V1 主循环的完整生命周期
人工智能·开源
三更两点3 小时前
2026年开源Agent工具栈
开源
咏方舟【长江支流】5 小时前
【开源】跨语言·跨平台·跨数据库(2) —— 用宝轻量框架设计必读
数据库·架构·开源·ai编程·咏方舟-长江支流·用宝框架
程序员吕洞宾7 小时前
开源多维表格SmartTable V1.6.1:工作流画布,让自动化工作流配置更直观、更方便
java·开源·自动化
雾沉川7 小时前
Netcatty 开源 SSH 客户端完整技术使用教程
运维·开源·ssh
t_guest7 小时前
LVGL学习(二)- 定时器
lvgl
t_guest8 小时前
LVGL学习(三)- 日志
入门·lvgl