lv_scr_mgr
lvgl 界面管理器
适配 lvgl 8.3
- 降低界面之间的耦合
- 使用较小的内存,界面切换后会自动释放内存
- 内存泄漏检测
使用方法
- 在lv_scr_mgr_port.h 中创建一个枚举,用于界面ID
- 为每个界面创建一个页面管理器句柄
- 将界面句柄添加到 lv_scr_mgr_port.c 数组中
- 在 lv_init() 后,对页面管理器进行初始化 lv_scr_mgr_init(NULL);
- 使用 lv_scr_mgr_switch 设置初始根界面
- 使用 lv_scr_mgr_push lv_scr_mgr_pop 对界面进行操作
git地址
- lv_scr_mgr.c
/**
*******************************CopyRight ************************************
* @file lv_scr_mgr.c
* @author zyf
* @date 2023-10-11 13:4:36
* @brief lvgl 页面管理器
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "lvgl.h"
#include "lv_scr_mgr.h"
typedef struct
{
uint32_t scr_cnt;
void* param;
const lv_scr_mgr_handle_t **handles;
#if LV_SCR_MGR_PRINTF_MEM
uint32_t *max_mem;
#endif
}scr_mgr_list_handle_t;
static scr_mgr_list_handle_t mgr_list;
static lv_scr_mgr_stack_node_t* mgr_stack_top = NULL;
static lv_scr_mgr_stack_node_t* mgr_stack_root = NULL;
static const lv_scr_mgr_handle_t* find_handle_by_id(uint32_t id)
{
for (int i = 0; i < mgr_list.scr_cnt; i++)
{
if (mgr_list.handles[i]->scr_id == id)
{
return mgr_list.handles[i];
}
}
return NULL;
}
#if LV_SCR_MGR_PRINTF_MEM
static uint32_t* find_mem_addr_by_id(uint32_t id)
{
for (int i = 0; i < mgr_list.scr_cnt; i++)
{
if (mgr_list.handles[i]->scr_id == id)
{
return &mgr_list.max_mem[i];
}
}
return NULL;
}
static void mem_max_printf(uint32_t id)
{
static uint32_t mem_max = 0;
lv_mem_monitor_t mon;
lv_mem_monitor(&mon);
if (mon.total_size - mon.free_size > mem_max)
{
mem_max = mon.total_size - mon.free_size;
LV_LOG_USER("used: %d (%d %%), frag: %d %%, biggest free: %d\n", mem_max,
mon.used_pct,
mon.frag_pct,
(int)mon.free_biggest_size);
}
}
static void page_mem_max_printf(uint32_t id)
{
lv_mem_monitor_t mon;
lv_mem_monitor(&mon);
/* 当前界面最大使用内存 */
uint32_t* page_max_mem = find_mem_addr_by_id(id);
if (mon.total_size - mon.free_size > *page_max_mem)
{
*page_max_mem = mon.total_size - mon.free_size;
LV_LOG_USER("page id %d, used: %d (%d %%), frag: %d %%, biggest free: %d\n", id, *page_max_mem,
mon.used_pct,
mon.frag_pct,
(int)mon.free_biggest_size);
}
}
static void anim_mem_max_printf(lv_event_t* e)
{
lv_event_code_t event_code = lv_event_get_code(e);
if (event_code == LV_EVENT_SCREEN_LOADED)
{
page_mem_max_printf((uint32_t)lv_event_get_user_data(e));
}
}
#endif
static void scr_mgr_stack_free(void)
{
lv_scr_mgr_stack_node_t* stack_node = NULL;
/* 释放界面栈 */
while (NULL != mgr_stack_top)
{
stack_node = mgr_stack_top->prev;
if(mgr_stack_top->handle->scr_destroy)
mgr_stack_top->handle->scr_destroy();
lv_mem_free((void*)mgr_stack_top);
mgr_stack_top = stack_node;
}
mgr_stack_root = NULL;
}
/**
* @brief 入栈
* @param tag 要入栈的句柄
* @return 栈顶句柄
*/
static lv_scr_mgr_stack_node_t* scr_mgr_stack_push(const lv_scr_mgr_handle_t* tag)
{
lv_scr_mgr_stack_node_t* stack_node = NULL;
stack_node = lv_mem_alloc(sizeof(lv_scr_mgr_stack_node_t));
LV_ASSERT_MALLOC(stack_node);
stack_node->handle = tag;
stack_node->next = NULL;
if (stack_node->handle->scr_first_create)
{
stack_node->handle->scr_first_create();
}
if (tag->scr_create)
{
stack_node->scr = tag->scr_create(stack_node->handle->scr_id, mgr_list.param);
}
else
{
LV_LOG_ERROR("no create fun!");
}
if (NULL == mgr_stack_top)
{
stack_node->prev = NULL;
mgr_stack_root = stack_node;
}
else
{
stack_node->prev = mgr_stack_top;
mgr_stack_top->next = stack_node;
}
mgr_stack_top = stack_node;
return stack_node;
}
static int32_t scr_mgr_stack_pop(int32_t n)
{
lv_scr_mgr_stack_node_t* stack_node = NULL;
int32_t i = n;
if ((NULL == mgr_stack_top) || (NULL == mgr_stack_top->prev))
{
return 0;
}
while (i)
{
if ((NULL == mgr_stack_top) || (NULL == mgr_stack_top->prev))
{
break;
}
stack_node = mgr_stack_top->prev;
if (mgr_stack_top->handle->scr_destroy)
{
mgr_stack_top->handle->scr_destroy();
}
lv_mem_free((void*)mgr_stack_top);
mgr_stack_top = stack_node;
i--;
}
if (NULL != mgr_stack_top->handle->scr_create)
{
mgr_stack_top->scr = mgr_stack_top->handle->scr_create(mgr_stack_top->handle->scr_id, mgr_list.param);
}
else
{
LV_LOG_ERROR("no create fun!");
}
if (i)
{
LV_LOG_WARN("stack pop %d, but stack is %d", n, n-i);
}
return n - i;
}
/**
* @brief 切换界面
* @param cur_scr 当前界面
* @param stack_node 目标界面句柄
* @param anim 切换界面动画开关
* 关闭界面切换动画,切换界面时会先创建一个新的空界面,切换到空界面后,
* 删除之前的界面,然后再创建切换到新界面,最后再删除中间界面。会节省内存。
* 关闭界面切换动画,切换界面时直接创建新界面,然后再用动画切换到新界面。
*
* @return true
*/
bool scr_mgr_switch(lv_obj_t* cur_scr, lv_scr_mgr_stack_node_t* stack_node, bool anim)
{
lv_scr_load_anim_t load_anim = LV_SCR_MGR_LOAD_ANIM_DEFAULT;
lv_obj_t* tmp_scr = NULL;
if (anim)
{
if ((stack_node->handle->anim_type != LV_SCR_LOAD_ANIM_NONE) && (LV_SCR_LOAD_ANIM_OUT_BOTTOM >= stack_node->handle->anim_type))
{
load_anim = stack_node->handle->anim_type;
}
#if LV_SCR_MGR_PRINTF_MEM
mem_max_printf(stack_node->handle->scr_id);
lv_obj_add_event_cb(stack_node->scr, anim_mem_max_printf, LV_EVENT_SCREEN_LOADED, (void*)stack_node->handle->scr_id);
#endif
lv_scr_load_anim(stack_node->scr, load_anim, LV_SCR_MGR_LOAD_ANIM_TIME, LV_SCR_MGR_LOAD_ANIM_DELAY, true);
}
else
{
if (NULL != cur_scr)
{
tmp_scr = lv_obj_create(NULL);
lv_scr_load(tmp_scr);
lv_obj_del(cur_scr);
cur_scr = NULL;
}
lv_scr_load(stack_node->scr);
#if LV_SCR_MGR_PRINTF_MEM
mem_max_printf(stack_node->handle->scr_id);
page_mem_max_printf(stack_node->handle->scr_id);
#endif
if (NULL != tmp_scr)
{
lv_obj_del(tmp_scr);
}
}
return true;
}
/**
* @brief 初始化界面管理器
* @param param 创建界面时的参数
* @return
*/
bool lv_scr_mgr_init(void* param)
{
mgr_list.param = param;
#if LV_SCR_MGR_REG_ENABLE
extern const lv_scr_mgr_handle_t* scr_mgr_scr_mgr_start;
extern const lv_scr_mgr_handle_t* scr_mgr_scr_mgr_end;
const lv_scr_mgr_handle_t** item = &scr_mgr_scr_mgr_start;
item++;
mgr_list.handles = item;
mgr_list.scr_cnt = 0;
for(;item < &scr_mgr_scr_mgr_end; item++)
{
mgr_list.scr_cnt++;
}
#else
mgr_list.scr_cnt = lv_scr_mgr_get_cnt();
mgr_list.handles = lv_scr_mgr_get_handles();
#endif
if (0 == mgr_list.scr_cnt)
{
LV_LOG_ERROR("no screen!");
return false;
}
#if LV_SCR_MGR_PRINTF_MEM
mgr_list.max_mem = lv_mem_alloc(mgr_list.scr_cnt * sizeof(uint32_t*));
LV_ASSERT(mgr_list.max_mem);
memset(mgr_list.max_mem, 0, mgr_list.scr_cnt * sizeof(uint32_t*));
#endif
return true;
}
void lv_scr_mgr_deinit(void)
{
mgr_list.param = NULL;
#if LV_SCR_MGR_PRINTF_MEM
lv_mem_free(mgr_list.max_mem);
#endif
scr_mgr_stack_free();
}
void lv_scr_mgr_param_set(void* param)
{
mgr_list.param = param;
}
void* lv_scr_mgr_param_get(void)
{
return mgr_list.param;
}
/**
* @brief 设置根界面
* @param id 根界面序号
* @param anim 动画开关
* @return
*/
bool lv_scr_mgr_switch(uint32_t id, bool anim)
{
const lv_scr_mgr_handle_t* tag_handle = find_handle_by_id(id);
const lv_scr_mgr_handle_t* cur_handle = NULL;
lv_scr_mgr_stack_node_t* stack_node = NULL;
lv_obj_t* cur_scr = NULL;
if (NULL == tag_handle)
{
LV_LOG_ERROR("no screen, id %d", id);
return false;
}
if (NULL != mgr_stack_top)
{
/* 栈内有界面 */
cur_handle = mgr_stack_top->handle;
cur_scr = mgr_stack_top->scr;
}
else
{
cur_scr = lv_scr_act();
}
scr_mgr_stack_free();
if ((NULL == cur_handle) || (tag_handle->scr_id == cur_handle->scr_id))
{
/* 没有界面切换,不使用动画效果 */
anim = false;
}
stack_node = scr_mgr_stack_push(tag_handle);
return scr_mgr_switch(cur_scr, stack_node, anim);
}
/**
* @brief 入栈一个新的界面
* @param id
* @param anim
* @return
*/
bool lv_scr_mgr_push(uint32_t id, bool anim)
{
const lv_scr_mgr_handle_t* tag_handle = find_handle_by_id(id);
lv_scr_mgr_stack_node_t* stack_node = NULL;
lv_obj_t* cur_scr = NULL;
if (NULL == tag_handle)
{
LV_LOG_ERROR("no screen, id %d", id);
return false;
}
if ((NULL == mgr_stack_top) || (NULL == mgr_stack_root))
{
LV_LOG_ERROR("no root screen, please use lv_scr_mgr_switch create root screen");
return false;
}
cur_scr = mgr_stack_top->scr;
stack_node = scr_mgr_stack_push(tag_handle);
return scr_mgr_switch(cur_scr, stack_node, anim);
}
/**
* @brief 出栈n个界面
* @param n 如果栈内界面没有n个,则返回根界面
* @param anim
* @return
*/
bool lv_scr_mgr_popn(uint32_t n, bool anim)
{
lv_obj_t* cur_scr = NULL;
if ((mgr_stack_top == NULL) || (mgr_stack_top->prev == NULL))
{
return false;
}
cur_scr = mgr_stack_top->scr;
scr_mgr_stack_pop(n);
return scr_mgr_switch(cur_scr, mgr_stack_top, anim);
}
/**
* @brief 出栈一个界面
* @param anim
* @return
*/
bool lv_scr_mgr_pop(bool anim)
{
return lv_scr_mgr_popn(1, anim);
}
/**
* @brief 退回到根界面
* @param anim
* @return
*/
bool lv_scr_mgr_pop_root(bool anim)
{
lv_scr_mgr_stack_node_t* stack_node = NULL;
lv_scr_mgr_stack_node_t* stack_top = NULL;
uint32_t cnt = 0;
if (NULL == mgr_stack_root || NULL == mgr_stack_top)
{
return false;
}
stack_top = mgr_stack_top;
while (stack_top != NULL)
{
cnt++;
stack_node = stack_top->prev;
stack_top = stack_node;
}
return lv_scr_mgr_popn(cnt-1, anim);
}
/**
* @brief 获取当前界面id
* @param
* @return
*/
int32_t lv_scr_mgr_get_cur_id(void)
{
if (NULL != mgr_stack_top && NULL != mgr_stack_top->handle)
{
return mgr_stack_top->handle->scr_id;
}
else
{
return -1;
}
}
/**
* @brief 获取根界面id
* @param
* @return
*/
int32_t lv_scr_mgr_get_root_id(void)
{
if (NULL != mgr_stack_root && NULL != mgr_stack_root->handle)
{
return mgr_stack_root->handle->scr_id;
}
else
{
return -1;
}
}
/************************ (C) COPYRIGHT ***********END OF FILE*****************/
- lv_scr_mgr.h
/**
*******************************CopyRight ************************************
* @file lv_scr_mgr.h
* @author zyf
* @date 2023-10-11 9:31:49
* @brief lvgl 页面管理器
*
******************************************************************************
*/
#ifndef _LV_SCR_MGR_H_
#define _LV_SCR_MGR_H_
/* Includes ------------------------------------------------------------------*/
#include "stdint.h"
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
/*!< 界面切换动画默认值
*/
#define LV_SCR_MGR_LOAD_ANIM_DEFAULT LV_SCR_LOAD_ANIM_MOVE_LEFT
#define LV_SCR_MGR_LOAD_ANIM_TIME 500
#define LV_SCR_MGR_LOAD_ANIM_DELAY 0
/*!< 内存泄漏检测 注意,页面管理器的push操作也会申请动态内存
* 如果使用动画,则需要将 lv_disp.c 中的 scr_anim_ready 改为如下
static void scr_anim_ready(lv_anim_t * a)
{
lv_disp_t * d = lv_obj_get_disp(a->var);
lv_event_send(d->prev_scr, LV_EVENT_SCREEN_UNLOADED, NULL);
if(d->prev_scr && d->del_prev) lv_obj_del(d->prev_scr);
d->prev_scr = NULL;
d->draw_prev_over_act = false;
d->scr_to_load = NULL;
lv_event_send(d->act_scr, LV_EVENT_SCREEN_LOADED, NULL);
lv_obj_remove_local_style_prop(a->var, LV_STYLE_OPA, 0);
lv_obj_invalidate(d->act_scr);
}
*/
#define LV_SCR_MGR_PRINTF_MEM 1
/* 使用分散加载的方式 将 lv_scr_mgr_handle_t 存放在特定段,
* 用户创建 lv_scr_mgr_handle_t 后可以在后面直接 使用 LV_SCR_MGR_REG() 对界面句柄进行注册
* 而不用将 界面句柄 添加到 lv_scr_mgr_port.c 数组中
*/
#define LV_SCR_MGR_REG_ENABLE 0
typedef struct
{
uint32_t scr_id; /*!< id */
lv_scr_load_anim_t anim_type; /*!< 切换动画类型 如果为空,则使用 LV_SCR_MGR_LOAD_ANIM_DEFAULT */
void (*scr_first_create)(void); /*!< lv_scr_mgr_switch lv_scr_mgr_push 函数会调用该创建函数 pop则不会调用 可以方便实现pop记住焦点 而push使用默认焦点 */
lv_obj_t* (*scr_create) (const uint32_t id, void* param); /*!< 创建界面,创建界面时不要使用 lv_scr_mgr_xxx 函数 */
void (*scr_destroy)(void); /*!< 删除界面的回调函数,一般用于删除如 lv_timer 等不会随界面自动删除的资源 */
}lv_scr_mgr_handle_t;
typedef struct _lv_scr_mgr_stack_node_t
{
const lv_scr_mgr_handle_t* handle;
lv_obj_t* scr;
struct _lv_scr_mgr_stack_node_t* prev;
struct _lv_scr_mgr_stack_node_t* next;
}lv_scr_mgr_stack_node_t;
#include "lv_scr_mgr_port.h"
#if LV_SCR_MGR_REG_ENABLE
#define ANONY_CONN(type, var) type var
#define ANONY_DEF(type,prefix) ANONY_CONN(type, prefix)
#define ANONY_TYPE(type,prefix) ANONY_DEF(type, prefix)
#if defined(__CC_ARM) || defined(__GNUC__) /* ARM,GCC*/
#define SECTION(x) __attribute__((section(x)))
#define USED __attribute__((used))
#elif defined (__ICCARM__) /*IAR */
#define SECTION(x) @ x
#define USED __root
#else
#error "Current tool chain haven't supported yet!"
#endif
#define _LV_SCR_MGR_REG(handle, level) \
USED ANONY_TYPE(const lv_scr_mgr_handle_t*, scr_mgr_##handle)\
SECTION("scr_mgr."level) = &(handle)
#define LV_SCR_MGR_REG(handle) _LV_SCR_MGR_REG(handle, "1")
#else
#define LV_SCR_MGR_REG(handle)
#endif
bool lv_scr_mgr_init(void* param);
void lv_scr_mgr_deinit(void);
void lv_scr_mgr_param_set(void* param);
void* lv_scr_mgr_param_get(void);
bool lv_scr_mgr_switch(uint32_t id, bool anim);
bool lv_scr_mgr_push(uint32_t id, bool anim);
bool lv_scr_mgr_popn(uint32_t n, bool anim);
bool lv_scr_mgr_pop(bool anim);
bool lv_scr_mgr_pop_root(bool anim);
int32_t lv_scr_mgr_get_cur_id(void);
int32_t lv_scr_mgr_get_root_id(void);
#ifdef __cplusplus
}
#endif
#endif /* _LV_SCR_MGR_H_ */
/************************ (C) COPYRIGHT *****END OF FILE*****************/
- lv_scr_mgr_port.c
/**
*******************************CopyRight ************************************
* @file lv_scr_mgr_port.c
* @author zyf
* @date 2023-10-11 13:51:24
* @brief lvgl 页面管理器 接口
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "lv_scr_mgr.h"
#include "lv_scr_mgr_port.h"
#if LV_SCR_MGR_REG_ENABLE
/* 无需用户修改 */
const lv_scr_mgr_handle_t scr_mgr_start = {0};
_LV_SCR_MGR_REG(scr_mgr_start, "0");
const lv_scr_mgr_handle_t scr_mgr_end = {0};
_LV_SCR_MGR_REG(scr_mgr_end, "2");
#else
/* 需要用户添加自己的界面句柄 */
extern const lv_scr_mgr_handle_t s_scr_mgr_handle_test1;
extern const lv_scr_mgr_handle_t s_scr_mgr_handle_test2;
extern const lv_scr_mgr_handle_t s_scr_mgr_handle_test3;
static const lv_scr_mgr_handle_t* scr_mgr_handles[] = {
& s_scr_mgr_handle_test1,
& s_scr_mgr_handle_test2,
& s_scr_mgr_handle_test3,
};
uint32_t lv_scr_mgr_get_cnt(void)
{
return sizeof(scr_mgr_handles) / sizeof(scr_mgr_handles[0]);
}
const lv_scr_mgr_handle_t** lv_scr_mgr_get_handles(void)
{
return scr_mgr_handles;
}
#endif
/************************ (C) COPYRIGHT ***********END OF FILE*****************/
- lv_scr_mgr_port.h
/**
*******************************CopyRight ************************************
* @file lv_scr_mgr_port.h
* @author zyf
* @date 2023-10-11 13:51:23
* @brief lvgl 页面管理器 接口
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef _LV_SCR_MGR_PORT_H_
#define _LV_SCR_MGR_PORT_H_
/* Includes ------------------------------------------------------------------*/
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum
{
eScreenUITest1 = 0,
eScreenUITest2,
eScreenUITest3
}lv_scr_mgr_id_e;
#if !LV_SCR_MGR_REG_ENABLE
uint32_t lv_scr_mgr_get_cnt(void);
const lv_scr_mgr_handle_t** lv_scr_mgr_get_handles(void);
#endif
#ifdef __cplusplus
}
#endif
#endif /* _LV_SCR_MGR_PORT_H_ */
/************************ (C) COPYRIGHT ***********END OF FILE*****************/