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):页面切换原理和页面管理实现 的相关文章

  • Android:基本 UI 组件

    前言 组件是 Android 程序设计的基本组成单元 通过使用组件可以高效地开发 Android 应用程序 文本类组件 Android 中提供了一些与文本显示 输入相关的组件 通过这些组件可以显示或输入文字 TextView 类 用于显示文
  • AFNetwork 作用和使用方法具体解释

    转自 http www maxiaoguo com clothes 269 html AFNetworking是一个轻量级的iOS网络通信类库 它建立在NSURLConnection和NSOperation等类库的基础上 让非常多网络通信功
  • Unity中Shader实现UI去色功能的实现思路

    文章目录 前言 一 在开发过程中 在UI中会涉及一些需要置灰UI的需求 有很多实现的方法 1 做两套纹理 通过程序控制切换 2 使用shader实现对纹理去色 二 这里主要记录用shader实现的思路 1 基础纹理的采样 2 支持组件中的调
  • 前端自测.

    交互 宽度1200px 表单校验 长度 敏感词汇 初始化 loading 结果 空 表单重复提交 loading 请求拦截 限流 数字 大数据 千分位 文本长度 省略号 数据查询结果校验 按条件查询结果是否正确 空数据传参 xx 参数头尾有
  • element-ui中日期区间组件

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

    命令行在外部 从命令行运行编辑项目 1 导航到您的 LauncherInstall VersionNumber Engine Binaries Win64 目录中 2 右键单击上 UE4Editor exe 的可执行文件 并选择创建快捷方式
  • Qt—帮助系统

    一个完善的应用程序应该提供尽可能丰富的帮助信息 Qt中可以使用工具提示 状态提示以及 What s This 等简单的帮助提示 也可以使用Qt Assistant来提供强大的在线帮助 简单的帮助提示 已经讲到了工具提示和状态提示 这里简单介
  • MapReduce作业状态一直为ACCEPTED解决过程

    toc 今天在测试Hadoop文件压缩功能时 在之前本地搭建的Hadoop集群上提交了一个MapReduce作业 但是提交后发现一直卡在那不动 18 07 20 17 21 50 WARN util NativeCodeLoader Una
  • (ps2019)Photoshop 2019 最新破解版下载

    Photoshop CC 2019新增功能 下载地址点我 新功能介绍 https helpx adobe com cn photoshop using whats new html 经过改良设计的内容识别填充 借助 Adobe Sensei
  • Unity中UI框架的使用1-添加面板、显示Loading页面

    其中BasePanel和Canvas都是挂在面板的预制物上的 1 导入我们的UI框架 本篇文章中有用的是两个UIPanelType NUIManager和NBasePanel 会放在文章最后供大家使用 2 先将我们做好的Panel设置成预制
  • Android4.0 SDK功能详解

    我在eoe的论坛找到的 就复制过来了 跟大家分享一下 Android 4 0 平台API等级 14 Android 4 0 是一次重要的平台发布版 为用户和应用程序开发者增加了大量的新特性 在下面我们将讨论的所有新特性和API中 因为它将
  • 【干货】如何实现WinApp的UI自动化测试?

    WinApp WindowsAPP 是运行在Windows操作系统上的应用程序 通常会提供一个可视的界面 用于和用户交互 例如运行在Windows系统上的Microsoft Office PyCharm Visual Studio Code
  • 【QView】基于QML的UI组件框架 之 AImage (图片)

    先上结果演示 环境 不说版本就是耍流氓 硬件 通用PC 手机 Jetson Xavier NX 套件 均测试有效 系统 Ubuntu 20 04 Android Windows 均测试有效 软件 基于QT6 2 4 Qml 功能描述 AIm
  • 基于Selenium和python的UI自动化测试方案

    一 概述 对于比较复杂的系统 每次有小的迭代测试同学不可能会把所有的流程验证一遍 如果开发无意改动影响了某些流程而测试又没测试到 就可能会出现生产问题 因此很有必要通过自动化的测试去确保系统的稳定性 自动化测试可以选择接口自动化测试和UI自
  • UI 易用性测试 以及自动化实现!

    GUI 是指图形用户界面 UI 是指用户界面 对于纯软件系统 这两者没有本质的区别 GUI易用性测试与 UI 易用性测试内容一致 但是如果测试的对象是一个产品 这两者则存在区别 对于产品 UI 则不仅仅包括 GUI 还包括产品硬件部分的测试
  • 【自动化测试】selenium元素定位方式大全!

    前言 当我们在使用selenium进行自动化测试工作时 元素定位是非常重要的一环 因为我们是借助脚本模拟我们通过鼠标和键盘对元素进行点击 输入内容和滑动操作的 所以准确的元素定位是我们执行测试脚本的重要一环 本文就来给大家介绍一下selen
  • 创意无限,绘图轻松——Sketch for Mac矢量绘图软件全面介绍

    在现代设计领域 矢量绘图软件是设计师们必不可少的工具之一 而在众多矢量绘图软件中 Sketch for Mac凭借其强大的功能和友好的用户界面脱颖而出 成为众多设计师的首选 Sketch for Mac是一款专为Mac用户开发的矢量绘图软件
  • Mac版 Photoshop 2021---PS2021

    Adobe Photoshop 2021是一款强大的图像处理软件 它可以帮助用户进行各种图像编辑 修饰和合成工作 这款软件拥有先进的图像处理技术 支持多种图像格式 可以轻松实现各种复杂的图像处理任务 它还提供了丰富的滤镜和工具 使用户可以自
  • 界面控件DevExpress WPF属性网格 - 让应用轻松显示编辑各种属性事件

    DevExpress WPF Property Grid 属性网格 灵感来自于Visual Studio Visual Studio启发的属性窗口 对象检查器 让在WPF应用程序显示和编辑任何对象的属性和事件变得更容易 P S DevExp
  • UI自动化测试之Jenkins配置

    背景 团队下半年的目标之一是实现自动化测试 这里要吐槽一下 之前开发的测试平台了 最初的目的是用来做接口自动化测试和性能测试 但由于各种原因 接口自动化测试那部分功能整个废弃掉了 其中和易用性有很大关系 另外 也和我们公司的接口业务也有关

随机推荐

  • 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有一个列表框 有三个选项 每个选项对应进入一个页面 假设此时我们的焦点