LVGL学习(3):页面切换原理和页面管理实现

2023-11-20

在LVGL中,大多情况下是有多个页面的,一般来说页面的切换有两种情况:

  1. 删除当前的页面,创建新页面加载
  2. 保留当前的页面,创建新页面加载

我们来分析一下这两种情况,比如页面1有一个列表框,有三个选项,每个选项对应进入一个页面。假设此时我们的焦点落在第二个选项上,然后点击进入页面2,我们要是想返回,我们是希望焦点还是保留在列表的第二个选项上的。基于这种情况来说,我们希望能够在保留页面1的同时(同时也保留了焦点),创建页面2并加载。同时对于页面2来说,只是想用户选择的时候再加载,每次进入都处于初始状态,所以在从页面2返回页面1时,我们会希望删除页面2,再创建页面1并加载。

所以这两种切换方式我们都需要了解,下面就来看看在LVGL中如何完成页面切换。

1 页面切换函数

首先,无论对于哪种情况,我们都需要创建一个新页面进行加载。在LVGL中是采用函数lv_scr_load来加载一个新页面的,它的调用关系如下:

lv_scr_load(scr)
	lv_disp_load_scr(scr)
		lv_scr_load_anim(scr, LV_SCR_LOAD_ANIM_NONE, 0, 0, false);

这里我们不对lv_scr_load_anim进行深入分析,我们只需要知道这个函数最终可以用来显示一个页面就行了,其中anim表示animation,即页面加载时可以有一些动画。lv_scr_load函数则是不用任何动画而是直接加载页面。所以我们也可以直接使用lv_scr_load_anim进行加载页面。

有了这个函数,实际上页面切换就很简单了,对于每个页面来说,一个lv_obj_t基础对象表示一个页面,我们只需要让所有的组件都以这个页面lv_obj_t为父类即可,然后用lv_scr_load_anim函数进行加载就行了。

隐藏页面切换法?
隐藏页面切换法就是调用lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN)lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN)来隐藏/显示页面以实现页面的切换。如果想多个页面显示在一起,即有上下层叠的关系,可以指定所有页面有一个共有的父类。
但对于lv_scr_load_anim函数来说,仅支持单个页面的显示,参数是哪个页面,显示的就是哪个页面,即使上一个页面没有被删除,也不会被显示出来。如果将所有的页面都建立于一个lv_obj上,不仅浪费内存,而且页面之间的管理也非常乱。所以在我看来,没有什么所谓的隐藏页面切换法。

2 页面切换的条件及原因

对于实际的页面切换来说,我们还需要考虑一些事,比如从当前页面切换出去,是要删除还是保留当前页面及其所有子对象。我们就以GUI Guider生成的代码为例,看看它是如何删除页面、如何调用lv_scr_load_anim进行页面的切换的,毕竟这种软件上生成的代码肯定是非常严谨的。


这里插一句嘴,网上很多信息都不太严谨,我之前下了一个别人的LVGL源码进行学习,用里面的页面切换代码在单片机中运行没问题,但在Codeblocks模拟就有可能会崩溃。这并不是CodeBlocks不兼容,而是代码本身就有内存越界访问的问题。这就让我想到了之前我用QT写的代码在Windows运行是正常的,一到Linux立马就崩了,实际上也是访问了一个空的内存造成的。

还有比如BootLoader,如果开启了某个外设的中断、Cache、GPIO时钟等,在退出BootLoader前一定要关掉,即恢复之前的状态。我有亲身经历,我的同事的BootLoader在退出前没有关串口的中断,然后进入APP后一个常量字符串的某一位莫名其妙的被修改了,然后来问我为什么。网上很多内容都是不严谨的,可能表面上确实可以用,但也留下了一些潜在的错误。我的建议是参考官方SDK或Github中Star很多的代码。


如下图所示,我们先创键两个页面:
在这里插入图片描述
我们希望页面一的按钮按下后切换到页面二,页面二的按钮按下后切换到页面一。在GUI Guider中选中按钮,在右侧添加Events,如下图所示,左边为页面一的Events,右边为页面二的Events。
在这里插入图片描述
以页面一为例,上图表示按钮按下后,将载入页面二。另外,我们注意到,在GUI Guider中,还有一个Delete current screen的选项,而且选上之后又出现一个Free memory of current screen before loading new screen选项。三种情况下,点击按钮后切换页面的代码如下所示:

/* 未选中Delete current screen */
lv_obj_t * act_scr = lv_scr_act();
lv_disp_t * d = lv_obj_get_disp(act_scr);
if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))
{
	if (guider_ui.screen_2_del == true)
		setup_scr_screen_2(&guider_ui);
	lv_scr_load_anim(guider_ui.screen_2, LV_SCR_LOAD_ANIM_NONE, 100, 100, false);
	guider_ui.screen_1_del = false;
}

/* 选中Delete current screen */
lv_obj_t * act_scr = lv_scr_act();
lv_disp_t * d = lv_obj_get_disp(act_scr);
if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))
{
	if (guider_ui.screen_2_del == true)
		setup_scr_screen_2(&guider_ui);
	lv_scr_load_anim(guider_ui.screen_2, LV_SCR_LOAD_ANIM_NONE, 100, 100, true);
	guider_ui.screen_1_del = true;
}

/* 选中Delete current screen和Free memory of current screen before loading new screen */
lv_obj_t * act_scr = lv_scr_act();
lv_disp_t * d = lv_obj_get_disp(act_scr);
if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))
{
	lv_obj_clean(act_scr);
	if (guider_ui.screen_2_del == true)
		setup_scr_screen_2(&guider_ui);
	lv_scr_load_anim(guider_ui.screen_2, LV_SCR_LOAD_ANIM_NONE, 100, 100, true);
	guider_ui.screen_1_del = true;
}

可以看出如果设置待加载的页面为要删除,则需要在每次加载新页面前,都需要调用setup_scr_screen_2来重新初始化页面中的组件,然后lv_scr_load_anim的最后一个参数auto_del设置为true,表示加载后自动删除原页面中的对象。如果想要在加载新页面前就把原页面中的内容释放掉,调用lv_obj_clean即可。

现在再来分析一下上面代码的共同特征:
(1)lv_obj_t * act_scr = lv_scr_act():获取当前屏幕中正在显示的页面的lv_obj_t类型的指针
(2)lv_disp_t * d = lv_obj_get_disp(act_scr);:获取当前屏幕对象关联的显示器对象
(3)d->prev_scr == NULL
LVGL支持页面切换动画,如从左到右切换,prev_scr就用作于此。当prev_scr不为NULL时,表示当前屏幕对象正在进行切换动画,并且可以通过prev_scr引用到前一个屏幕对象。当prev_scrNULL时才能切换页面,这样做是为了防止在动画执行过程中对前一个屏幕对象进行删除操作,以及避免可能的资源冲突或不一致性
(4)(d->scr_to_load == NULL || d->scr_to_load == act_scr)
只有在scr_to_load 等于NULLact_scr时才能切换页面。NULL表示当前没有在执行切换页面的动画(已经执行完毕),act_scr表示当前正处于从别的页面切换为当前页面的过程中。其它情况下,如当前页面1(act_scr)正准备切换到页面2时,执行切换到页面3是不被允许的。
(5)lv_obj_clean
表示在载入新的页面之前,删除当前页面的所有子对象。但当lv_scr_load_anim的最后一个参数为true时,会在下一次切换屏幕时调用lv_obj_del删除前一个页面及其子对象。但实际上我们先删除其子对象也不影响动画切换的效果,这是因为子对象已经绘制在act_scr中了,我们只需要当前页面的一个显示状态即可完成动画的过渡效果。

这里我仅仅是用文字对函数的执行过程进行了描述,具体的原理请参考lv_disp.c。如果有必要的话,我会写一篇源码分析的文章。

3 页面切换的模块化实现

从上面可以发现,其实页面切换就是调用那几行代码,但是在每个页面的回调函数中都写那几行又感觉有点冗余,所以干脆我们就写一个页面切换的函数,将所有页面管理起来,调用函数进行相互之间的切换。
(1)定义页面结构体PageStruct_t和页面标识符PAGE_ID

typedef enum
{
	PAGE_NULL = -1,
	/* 假设有两个页面:LOGO和MAIN,增加页面后自行在此添加对应的枚举类型 */
	PAGE_LOGO, 
	PAGE_MAIN,
}PAGE_ID;

typedef void(*page_init_handle)(lv_obj_t* root);
typedef struct
{
	uint8_t page_id;
	lv_obj_t* root;
	page_init_handle init_handler;  //页面中所有组件的初始化函数,以root为父对象
}PageStruct_t;

(2)添加和创建页面
这里实现了一个链表,来管理每个界面,链表实现很普遍而代码量很长,就不贴代码了。page_findlist_rpush为链表相关函数。

static void page_add(PageStruct_t* page)
{
	/* 遍历链表看是否page_id是否被加入过 */
	list_node_t* node = page_find(page->page_id);
	if(node != NULL) 
	{
		PRINTF("page has been added!\r\n");
		return;
	}
	node = list_rpush(page_list, list_node_new(page));
	if(node != NULL)
	{
		PRINTF("page %d adds successfully!\r\n", page->page_id);
	}
}

static PageStruct_t* page_create(uint8_t id)
{
	PageStruct_t* page = NULL;
	list_node_t* node = page_find(id);
	if(node != NULL)
	{
		page = node->val;
		page->root = lv_obj_create(NULL);
		/* 每个页面的大小与LCD屏幕大小对应 */
		lv_obj_set_size(page->root, DEMO_PANEL_WIDTH, DEMO_PANEL_HEIGHT);
		page->init_handler(page->root);
	}
	
	return page;
}

(3)页面切换
页面切换实际上就是在刚刚GUI Guider生成的代码的基础上加上了链表的查询操作。这里当del为1时,默认在切换页面前都调用lv_obj_clean把当前页面的子对象清理掉。

/*
 * @param:arg 页面ID
 * @param:del 页面内存释放标志
 */
void page_callback(PAGE_ID arg, lv_scr_load_anim_t animation, bool del)
{
	uint8_t id = (uint8_t)arg;
	PageStruct_t *page;
	lv_obj_t *act_scr = lv_scr_act();
	lv_disp_t *d = lv_obj_get_disp(act_scr);
	list_node_t* p_node = page_find_root(act_scr);

	if(p_node == NULL)
	{
		return;
	}
	page = p_node->val;

	if(d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr))
	{
		if(del)
		{
			lv_obj_clean(act_scr);
			page->root = NULL;
		}
		/* 根据待切换屏幕的page_id找到其结构体 */
		p_node = page_find(id);
		if(p_node == NULL) return;
		//page_cur = id;
		page = p_node->val;
		/* 如果屏幕中的组件被删除了,重新创建 */
		if(page->root == NULL)
		{
			page_create(id);
			if(page->root == NULL) return;
		}
		/* 加载新页面:这里固定了animation的时间 */
		lv_scr_load_anim(page->root, animation, 150, 50, del);
	}
}

(4)LOGO页面添加例子

static PageStruct_t page_logo = {
	.page_id = PAGE_LOGO,
	.init_handler = gui_logo_init,   //初始化页面中的各个组件的函数
	.root = NULL,
};
page_add(&page_logo);
page_callback(PAGE_LOGO,  LV_SCR_LOAD_ANIM_MOVE_LEFT,  0);

本文就不贴完整的代码了,实际上就缺一个链表的实现,因为这与本文的内容无关。大家自己去github找一个链表实现一下,同时也有助于理解代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LVGL学习(3):页面切换原理和页面管理实现 的相关文章

  • 突破自定义View性能瓶颈

    在Android应用程序中 自定义View是一个非常常见的需求 自定义View可以帮助您创建独特的UI元素 以满足您的应用程序的特定需求 然而 自定义View也可能会导致性能问题 特别是在您的应用程序需要处理大量自定义View的情况下 在本
  • QLine设置为虚线点线双线改变颜色

    实线 solid 虚线dashed 点线dotted 双线double 1 双线 border top 3px double black 线高度设置为3才能看到效果 2 点线 border top 2px dotted black 线高度设
  • Qt信号与槽的链接3种方法详解

    转载自 14条消息 Qt5 9 2 VS2017入门实例 透彻解析 冯爽朗的博客 CSDN博客 vs qt 示例 由于现在书上介绍的大部分都是Qt Creator 并没有介绍Qt VS的教程 虽然说很相似 但是也有很多地方不同 对于初学者特
  • 分享 20 道关于 React 开发相关的面试题及答案

    React 面试可能你会觉得有点吓人 为了帮助您自信并准备好迎接下一次面试 我们列出了 20 个常见的 React 问题和参考答案 希望通过本篇文章的内容 能够帮助你重新温习你的 React 知识 复习重要概念 并为你的下一次面试做好更充分
  • 前端自测.

    交互 宽度1200px 表单校验 长度 敏感词汇 初始化 loading 结果 空 表单重复提交 loading 请求拦截 限流 数字 大数据 千分位 文本长度 省略号 数据查询结果校验 按条件查询结果是否正确 空数据传参 xx 参数头尾有
  • app上架流程的整理

    app的上架流程 一 准备工作 首先需要有开发者账号 企业级的账号是299 个人开发者账号是99 没有的话可以登录http developer apple com 自行申请 假如你已经有账号了 进入苹果官网点击Accout登录 二 申请证书
  • element-ui中日期区间组件

    elementui中日期组件使用 最长只能选择3个月 不限制禁用日期 描述 时间组件代码 描述 点击 确定 按钮进行验证 点击 清空 按钮 清空输入框中的数据 时间范围不能超过3个月 并添加快捷选择今天 最近一周 最近一月 最近3个月 以下
  • 一些大厂的开源平台

    百度 http fex baidu com http efe baidu com 饿了么 https fe ele me 腾讯 http www alloyteam com 美团 https tech meituan com 滴滴 http
  • Qt5学习之路(vs2012下创建一个QT应用程序)2013-10-14

    刚开始学习QT在网上找的资料基本都是使用QT Create进行开发的 VS下开发的学习资料感觉很少很难找的到 视频教程也基本没看到过貌似 因为我们研发中心是使用MFC进行开发开发工具是VS2010 使用QT开发的话基本我们不会再使用QT C
  • UE4命令行使用,解释

    命令行在外部 从命令行运行编辑项目 1 导航到您的 LauncherInstall VersionNumber Engine Binaries Win64 目录中 2 右键单击上 UE4Editor exe 的可执行文件 并选择创建快捷方式
  • QT-----ChartView控件的使用

    chartview可用于制作折线图 饼状图 条行 柱状 直方图系等来体现数据数据变化起浮 以下案例以折线图举例 1 使用ChartView控件 注意点 1 要在pro文件中加入 QT widgets 2 主界面应当使用QApplicatio
  • element ui的el-tree多选树(复选框)父子节点关联不关联的问题,选中当前节点,他的子节点和父节点是否被选中,非常详细

    element ui的el tree多选树 复选框 父子节点关联不关联的问题 选中当前节点 他的子节点和父节点是否被选中 非常详细 属性check strictly 官方文档提供属性check strictly 在显示复选框的情况下 是否严
  • MapReduce作业状态一直为ACCEPTED解决过程

    toc 今天在测试Hadoop文件压缩功能时 在之前本地搭建的Hadoop集群上提交了一个MapReduce作业 但是提交后发现一直卡在那不动 18 07 20 17 21 50 WARN util NativeCodeLoader Una
  • ESP32C3 移植ST7735 LVGL

    关于lvgl LVGL是一个C语言编写的免费的开源图形库 其提供了用于嵌入式GUI的各种元素 用户可以利用丰富的图形库资源 在消耗极低内存的情况下构建视觉效果丰富多彩的GUI 只需 64kB 闪存和 8kB RAM 就足以满足简单的用户界面
  • string (std::string)转换为QString的用法(含中文)

    string s 123 QString str QString fromStdString s 含中文时的转换 std string str 你好世界 QString Name QString fromLocal8Bit str c st
  • Unity中UI框架的使用1-添加面板、显示Loading页面

    其中BasePanel和Canvas都是挂在面板的预制物上的 1 导入我们的UI框架 本篇文章中有用的是两个UIPanelType NUIManager和NBasePanel 会放在文章最后供大家使用 2 先将我们做好的Panel设置成预制
  • Swift 响应式编程:简化 KVO 观察与 UI 事件处理 | 开源日报 No.110

    ReactiveX RxSwift Stars 23 8k License MIT RxSwift 是 Reactive Extensions 标准的 Swift 特定实现 它提供了 Observable 接口来表达计算的通用抽象 该项目旨
  • element ui弹窗在别的弹窗下方,优先级不高的问题

    在 弹窗 的标签中加入append to body即可解决该问题
  • element ui弹窗在别的弹窗下方,优先级不高的问题

    在 弹窗 的标签中加入append to body即可解决该问题
  • Vue + Element-ui组件上传图片报错问题解决方案

    在使用Vue和Element ui组件上传图片时 可能会遇到一些报错问题 以下是一些常见的问题及解决方案 报错 TypeError Cannot read property name of undefined 解决方案 这个错误通常是因为在

随机推荐

  • linux共享内存面试题,linux系统工程师面试题(附答案)

    1 查看Linux系统当前单个共享内存段的最大值 命令 ipcs m ipcs a 2 用什么命令查询指定IP地址的服务器端口 题意应该是 nmap 和nbtscan 命令来扫吧 3 crontab中用什么命令定义某个程序执行的优先级别 n
  • Gurobi:使用Java+Gurobi建立一个小数学模型

    Gurobi 使用Java Gurobi建立一个小数学模型 按变量进行建模 按列进行建模 模型的求解结果 现在基本上都流行python gurobi java cplex进行建模 但是由于java相较于python还是具有显著的速度优势 于
  • 后疫情时代企业云原生成本优化指南

    在本篇文章的末为还有福利 在等着大家哦 前言 近年来 公有云 混合云等技术在全球迅速发展 云的普及度越来越高 Docker Kubernetes DevOps Service Mesh等云原生技术蓬勃发展 但在 上云 之后 企业却往往发现
  • 《数据库系统内幕》笔记 —— LSM树与OceanBase

    本文为 数据库系统内幕 第7章的笔记与心得 因为看到OceanBase底层也使用LSM树的实现作为存储引擎 因此特地记下笔记 详见OceanBase文档 https www oceanbase com docs community obse
  • Opencv学习笔记(三)线性及非线性滤波

    大纲 1 滤波综述 2 方框滤波 3 均值滤波 4 高斯滤波 5 中值滤波 6 双边滤波 一 滤波综述 图像的滤波指的是在尽量保证图像细节特征的的情况下对图像中的噪声进行抑制 又因为图像的能量大部分集中在低频或者中频的区域 图像大部分区域是
  • Scrum

    产品列表梳理会 Backlog Refinement Meeting 会议目的 Refinement 这个词是加工 提炼的意思 在scrum里 其实就是对下阶段的需求做一个讨论 澄清 细化的一个活动 希望通过这个活动 使得团队能对后续阶段的
  • 默认构造函数、拷贝构造函数、析构函数、赋值构造函数

    最近老是有人问我拷贝构造函数和赋值构造函数 说实话 我会用 但这个概念还真是搞不太清楚 真烦 概念问题少问我 学习笔记 1 析构函数 每个类只有一个析构函数 2 构造函数 每个类可以有多个构造函数 包括 默认构造函数 拷贝构造函数 赋值构造
  • Redis 7.0 核心技术、实战应用、面试题

    Redis 7 0 核心技术与实战应用 Redis 入门概述 01 Redis 是什么 Redis REmote Dictionary Server 远程字典服务器 官网介绍 https redis io docs about 官网定义 R
  • RabbitMQ消息丢失的场景,如何保证消息不丢失?(详细讲解,一文看懂)

    目录 一 RabbitMQ消息丢失的三种情况 二 RabbitMQ消息丢失解决方案 1 针对生产者 方案1 开启RabbitMQ事务 方案2 使用confirm机制 2 针对RabbitMQ 1 消息持久化 2 设置集群镜像模式 3 消息补
  • HDMI CEC协议

    1 前言 本文档仅作为本人记录使用 主要根据工作使用及 HDMI Specification 1 4a pdf 进行终结得出 若有不足会后续补充 2 CEC简介 CEC Consumer Electronics Control 是一套完整的
  • jenkins安装出现该实例似乎已离线等报错和如何卸载干净Jenkins的解决方案

    前段时间在准备使用Jenkins来实现Android自动化打包 但是在安装Jenkins的过程中出现了问题 在安装过程中出现 Jenkins实例似乎已离线 需要我配置代理 还有一个离线安装的文档 可是根据文档并没有明确说明怎么配置 然后我就
  • WPS Office 漏洞复现

    前言 此文章仅用于技术交流 严禁用于对外发起恶意攻击 一 产品简介 WPS Office是金山软件公司开发的 中国领先的办公软件套件 包含文字 表格和演示三个组件 支持创建 编辑各种文档 并具有强大的数据计算 统计和分析功能 其特点包括全面
  • Git提交代码步骤

    目录 1 Git提交代码步骤 1 1 第1步 同步远程仓库代码 git pull 1 2 第1步 查看当前状态 git status 1 3 第2步 提交代码到本地git缓存区 git add 1 4 第3步 推送代码到本地git库 git
  • 让csdn中的静态图动起(firemonkey)

    缘起 错过了登月50周年的日子 7 20 看到了如下卡通图童心大发 让它动起来 https blog csdn net csdnnews article details 96403350 代码下载 链接 https pan baidu co
  • CI/CD

    CICD 是 持续集成 Continuous Integration 持续交付和持续部署 Continuous Deployment 简称 指在开发过程中自动执行一系列从开发到部署的过程中 尽量减少人工的介入 CI CD AND CD CI
  • C++学习笔记12:输入输出流实例整理(文本文件读写,二进制文件读写,一组数据的文件读写,随机访问文件实例

    这也太难记了555老阔疼 文件读写示例 include
  • Kubernetes APIServer,Etcd,controller manager,scheduler 高可用原理

    高可用背后的原理 这两个月和博云合作的项目是要用于客户生产环境的 这个和我以前做的东西有很大的不同 所有基础架构必须给出高可用的解决方案 在这之前我只做过一些流量较小的用户产品或者一些原型项目 一开始基础架构都只给出了单节点的解决方案 结果
  • 微信小程序--给头像添加logo(生成海报同理)

    实现给图片添加logo或者生成海报 其原理是使用canvas 用canvas绘制出想要的图片进行保存 1 在wxml文件中添加canvas canvs层级太高 所以将它定位到屏幕外 不影响页面
  • 嵌入式资源网站

    原题地址 http blog csdn net ce123 article details 6724127 一 MailList 1 MailList大全 网址 http news gmane org 描述 可以查到绝大部分开源项目的Mai
  • LVGL学习(3):页面切换原理和页面管理实现

    在LVGL中 大多情况下是有多个页面的 一般来说页面的切换有两种情况 删除当前的页面 创建新页面加载 保留当前的页面 创建新页面加载 我们来分析一下这两种情况 比如页面1有一个列表框 有三个选项 每个选项对应进入一个页面 假设此时我们的焦点