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

news2024/11/24 15:56:33

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

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

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

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

文章目录

  • 1 页面切换函数
  • 2 页面切换的条件及原因
  • 3 页面切换的模块化实现

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函数来说,仅支持单个页面的显示,参数是哪个页面,显示的就是哪个页面,即使上一个页面没有被删除,也不会被显示出来。所以在我看来,没有什么所谓的隐藏页面切换法。

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;

	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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/613748.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VESC操作入门(三)——PPM输入控制和ADC输入控制

目录 一、PPM输入控制1.1、硬件准备1.2、PPM信号1.3、校准电机1.4、输入设置 二、ADC输入控制2.1、硬件准备2.2、更改固件2.3、电压信号2.4、校准电机2.5、输入设置 三、电动车转把控制3.1、转把说明3.2、转把测试 四、ADC的其它模式4.1、Current No Reverse Brake ADC24.2、Cu…

Windows 安装Redis教程(图文详解)_下载使用redis_Redis可视化_配置Redis环境变量

下载、安装和配置 给出自己的百度网盘链接: 链接:https://pan.baidu.com/s/14uO7jSm0DuoBWBaFO-obTw 提取码:1234 1. Redis下载 由于 Redis 官网没有提供 windows 版本的,只能去 github 上下载。 1、下载地址:github链…

【C/C++】基础知识之bool布尔类型

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

Java 服务端生成动态 Word 文档下载

需求&#xff1a;某些合同&#xff0c;被制作成模板&#xff0c;以 Word 格式保存&#xff0c;输入相关的内容参数最终生成 Word 文档下载。这是企业级应用中很常见的需求。 解决方案&#xff1a;无非是模板技术&#xff0c;界定不变和变的内容&#xff0c;预留插值的标记&…

创新书荐|《影响力经济》如何在社交媒体上寻求可信的影响力

在这个越来越不可预测的经济体中&#xff0c;贫富悬殊、大规模裁员等让许多人都觉得踏上稳定的职业道路遥不可及&#xff0c;自由职业听起来是最好的选择。宾夕法尼亚大学研究人员Emily Hund在她的新书《影响力经济》很好地讲述了年轻创意人士如何将互联网从分散的网页集合转变…

【目标检测——YOLO系列】YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》

YOLOv1 —《You Only Look Once: Unified, Real-Time Object Detection》 论文地址&#xff1a;1506.02640] You Only Look Once: Unified, Real-Time Object Detection (arxiv.org) 代码地址&#xff1a;pjreddie/darknet: Convolutional Neural Networks (github.com) 1、Y…

log4j 2自动配置的优先级顺序

log4j 2按照下面优先级由高到低的顺序查找使用日志的配置&#xff1a; 1、系统变量log4j2.configurationFile中指明的配置文件&#xff1b; 2、类路径上的log4j2-test.properties配置文件&#xff1b; 3、类路径上的log4j2-test.yaml 或者 log4j2-test.yml配置文件&#xff1b;…

【C++】C++中的I/O类总结——上篇

title: 【C】C-中的I/O类总结 tags: C description: ’ ’ categories: C date: 2023-06-05 00:36:59 引入 #include <iostream>int main(){std::cout<<"Hello World!"<<std::endl;}我们在学习C时&#xff0c;往往都是从上面这段程序开始的 也就…

React - Mobx

Mobx 简介 mobx是一个可以和React良好配合的集中状态管理工具&#xff0c;和Redux解决的问题相似&#xff0c;都可以独立组件进行集中状态管理 优势 简单 编写无模板的极简代码精准描述你的意图 轻松实现最优渲染 依赖自动追踪&#xff0c;实现最小渲染优化 架构自由 可…

Flutter - 一行命令解决多个pubspec.yaml文件的依赖项问题

文章目录 前言开发环境Flutter内置命令一行命令实现1. 命令使用2. 命令解释3. 命令扩展 最后 前言 项目为了模块化&#xff0c;创建了一堆Package和Plugin&#xff0c;这么做没什么问题&#xff0c;但是遇到Flutter SDK目录路径变化或者其他一些情况导致需要重新获取依赖项时就…

50+常用的广告联盟术语 (常用缩写)

广告联盟术语是指与广告联盟业务有关的行话和缩写。这些术语通常用于描述商业模型、营销策略、流量源、收益模型等方面的概念。了解广告联盟术语对于广告主、联盟会员、广告服务提供商等参与者都非常重要&#xff0c;因为它们可以帮助他们更好地理解广告联盟业务&#xff0c;提…

IDEA 2022.3.3 创建SpringBoot项目

目录 步骤01&#xff1a;快速创建项目 步骤02&#xff1a;选择依赖 步骤03&#xff1a;pom文件中版本问题 ​步骤04&#xff1a;启动测试 4.1、认识引导类 4.2、创建Controller类进行测试 可能遇到的问题及解决方案 附件1&#xff1a;pom文件源码 附件2&#xff1a;项…

华为OD机试题【食堂供餐】【2023 B卷 100分】

文章目录 &#x1f3af; 前言&#x1f3af; 题目描述&#x1f3af; 解题思路&#x1f4d9; Python代码实现&#x1f4d7; Java代码实现&#x1f4d8; C语言代码实现 &#x1f3af; 前言 &#x1f3c6; 《华为机试真题》专栏含2023年牛客网面经、华为面经试题、华为OD机试真题最…

Python使用正则表达式识别代码中的中文、英文和数字实例演示

Python 正则表达式识别代码中的中文、英文和数字 识别中文识别英文识别数字拓展 在文本处理和数据分析中&#xff0c;有时候需要从代码中提取出其中包含的中文、英文和数字信息。正则表达式是一种强大的工具&#xff0c;可以帮助我们实现这一目标。本文将分三个部分详细介绍如何…

chatgpt赋能python:Python如何倒序输出一组数

Python如何倒序输出一组数 Python是一种广泛使用的高级编程语言&#xff0c;由于其易读性和简洁性&#xff0c;Python已成为Web开发、数据分析以及人工智能等方向的首选语言。而在程序编写过程中&#xff0c;倒序输出一组数也是一个经常用到的操作。在本文中&#xff0c;我们将…

ActiveReportsJS 4.0.2 Crack ActiveReportsJS New

ActiveReportsJS - 高级 JavaScript 报告解决方案 ActiveReportsJS 是一个强大的 Web 应用程序报告工具&#xff0c;它允许开发人员和报告作者轻松地在他们的应用程序中设计和显示报告。凭借广泛的功能&#xff0c;例如向下钻取、运行时数据过滤和参数驱动的报告&#xff0c;以…

基于时间的访问控制列表(ACL)配置实验

基于时间的访问控制列表&#xff08;ACL&#xff09;配置实验 【实验目的】 掌握基于时间的ACL配置。认识给予时间的ACL的作用。验证配置。 【实验拓扑】 实验拓扑如下图所示。 设备参数如下表所示。 设备 接口 IP地址 子网掩码 默认网关 R1 S0/3/0 192.168.1.1 255…

24万字智慧城市时空信息云平台 大数据一体化 解决方案word

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除篇幅有限&#xff0c;无法完全展示&#xff0c;喜欢资料可转发评论&#xff0c;私信了解更多信息。 第二章 XX新型智慧城市总体设计 2.1 新型智慧城市核心技术 2.2 新型智慧城…

chatgpt赋能python:Python如何倒着循环:一步步教你倒序遍历序列

Python如何倒着循环&#xff1a;一步步教你倒序遍历序列 Python是一种高级编程语言&#xff0c;因其语法简单易学&#xff0c;常被用于数据分析、机器学习、自然语言处理等领域。在实际开发中&#xff0c;我们经常需要遍历序列。有时需要倒着循环序列&#xff0c;本文将详细介…

Roop:Colab脚本使用方法!

​AI领域人才辈出&#xff0c;突然就跳出一个大佬“s0md3v”&#xff0c;开源了一个单图就可以进行视频换脸的项目。 项目主页给了一张换脸动图非常有说服力&#xff0c;真是一图胜万言。 快速在本地配置一个环境&#xff0c;验证了一下&#xff0c;确实还不错。主要是&#xf…