LVGL学习笔记(八)
多个屏幕的切换&动画
前言
在前面的笔记中,我们了解了LVGL按键、标签等基础控件的使用,学习不同页面布局以及事件、定时器等LVGL核心功能。然而,在实际的LVGL开发中,项目中有很大的概率不只有一个界面。所以本篇笔记我们围绕学习多个界面的创建、切换、删除等基本功能展开,最后实现使用按键切换两个不同的界面。
在LVGL中,如何实现多个界面的切换?一共有几种方法?
在 LVGL 中实现多个界面切换,本质上是管理对象树的显示与隐藏。可以初步的分为如下的5种方法:
1.屏幕全切换
使用函数lv_scr_load() 可以将整个现象界面替换,达到屏幕切换的功能。其函数原型如下:
staticinlinevoidlv_scr_load(lv_obj_t*scr){lv_disp_load_scr(scr);}voidlv_disp_load_scr(lv_obj_t*scr){lv_scr_load_anim(scr,LV_SCR_LOAD_ANIM_NONE,0,0,false);}其本质是调用了一个无动画的lv_scr_load_anim()函数,该函数的原型会在下面提到,这里不做展开。
但需要注意的是,在LVGL中,由于lv_obj_create(NULL) 创建的Screen只是一个没有父对象的容器,而lv_scr_load()也只是把"当前激活指针"换掉,使刷新的界面发生变化。故在页面比较少的时候,使用这种方式切换界面不会出现大的问题,但在复杂的项目中,如超过3个以上的界面需要切换的情况下,必须要进行界面管理,而LVGL官方并没有提供专门的"页面管理器",故需要开发人员自己进行实现。该部分的内容后面的笔记中会具体展开,本篇笔记不展开讨论。
2.屏幕动画切换
使用函数lv_scr_load_anim()进行界面切换,其函数原型如下:
voidlv_scr_load_anim(lv_obj_t*new_scr,lv_scr_load_anim_tanim_type,uint32_ttime,uint32_tdelay,bool auto_del)其参数内容说明如下:
参数 说明 new_scr 要切换到的目标屏幕对象 anim_type 动画类型枚举 time 动画持续时间,单位毫秒 delay 延迟多少毫秒后才开始动画 auto_del 动画结束后是否自动删除旧屏幕对象 常用动画效果如下所示:
枚举值 效果 LV_SCR_LOAD_ANIM_NONE无动画 LV_SCR_LOAD_ANIM_MOVE_LEFT/RIGHT/TOP/BOTTOM新旧屏一起滑动 LV_SCR_LOAD_ANIM_OVER_LEFT/RIGHT/TOP/BOTTOM新屏从边缘覆盖 LV_SCR_LOAD_ANIM_FADE_ON淡入效果 LV_SCR_LOAD_ANIM_OUT_LEFT旧屏滑出,新屏原地显现 3.使用FLAG 控制对象隐藏/显示
不切换 Screen,而是在同一个 Screen 内创建多个容器(页面),通过隐藏/显示标志位来切换。
举例代码如下:
// 创建两个界面(父对象为当前活动屏幕)lv_obj_t*Screen1=lv_obj_create(lv_scr_act());lv_obj_t*Screen2=lv_obj_create(lv_scr_act());// 切换:隐藏 page1,显示 page2lv_obj_add_flag(Screen1,LV_OBJ_FLAG_HIDDEN);// 隐藏lv_obj_clear_flag(Screen2,LV_OBJ_FLAG_HIDDEN);// 显示函数lv_obj_add_flag(),函数原型如下:
voidlv_obj_add_flag(lv_obj_t*obj,lv_obj_flag_tf)其参数内容说明如下:
参数 说明 obj 目标对象 f 要添加的标志 常用添加的标记如下所示:
标志 说明 LV_OBJ_FLAG_CLICKABLE 隐藏对象 LV_OBJ_FLAG_HIDDEN 使对象可点击 LV_OBJ_FLAG_SCROLLABLE 使对象可滚动 LV_OBJ_FLAG_SCROLL_ON_FOCUS 当对象获得焦点时滚动 LV_OBJ_FLAG_EVENT_BUBBLE 允许事件冒泡到父对象 LV_OBJ_FLAG_GESTURE_BUBBLE 允许手势事件冒泡到父对象 LV_OBJ_FLAG_FLOATING 使对象浮动(不占据布局空间) LV_OBJ_FLAG_IGNORE_LAYOUT 忽略布局更新 LV_OBJ_FLAG_ADV_HITTEST 启用高级命中测试(允许自定义命中区域) 函数lv_obj_clear_flag(),函数原型如下:
voidlv_obj_clear_flag(lv_obj_t*obj,lv_obj_flag_tf)参数同函数lv_obj_add_flag(),都无返回值。
4.更改父对象
将页面从屏幕树上"摘下来"再"挂上去",实现按需加载。
举例代码如下:
// 切换时:将Screen1移出渲染树,将Screen2挂回屏幕lv_obj_set_parent(Screen1,NULL);// 脱离渲染树(不显示,不占用渲染资源)lv_obj_set_parent(Screen2,lv_scr_act());// 挂载到当前屏幕函数lv_obj_set_parent()的函数原型如下所示:
voidlv_obj_set_parent(struct_lv_obj_t*obj,struct_lv_obj_t*parent);该函数在前面笔记讲解父对象时有过介绍,这里不再次展开阐述。
5.使用选项卡视图
LVGL 提供的内置组件,自带滑动切换和指示器。
示例代码如下:
lv_obj_t*tabview=lv_tabview_create(lv_scr_act(),LV_DIR_TOP,50);lv_obj_t*tab1=lv_tabview_add_tab(tabview,"设置");lv_obj_t*tab2=lv_tabview_add_tab(tabview,"系统");// 在各 tab 中添加内容即可lv_label_create(tab1);lv_label_create(tab2);Tabview(选项卡)对象可用于组织选项卡中的内容。选项卡对象是由其他控件组件而成的。
简单来说,lv_tabview 是一个由顶部/侧边按钮栏和内容显示区组成的复合对象。
按钮栏:包含多个标签按钮,点击可切换页面。
内容显示区:显示当前激活。使用 lv_tabview 非常简单,只需要三步:创建控件 -> 添加标签 -> 添加内容。
可以通过函数lv_tabview_create()来实现创建控件,其函数原型如下:
lv_obj_t*lv_tabview_create(lv_obj_t*parent,lv_dir_ttab_pos,lv_coord_ttab_size)其参数内容说明如下:
参数 说明 parent 对象 tab_pos 按钮栏的位置 tab_size 按钮栏的高度 返回值为指向新创建的 TabView 对象本身。
可以通过函数lv_tabview_add_tab()来添加新选项卡,可以在其上创建选项卡的内容,其函数原型如下:
lv_obj_t*lv_tabview_add_tab(lv_obj_t*obj,constchar*name)参数obj为其对象,name为标签名,返回值为指向可以创建选项卡内容的对象指针。
使用函数lv_label_create()创建选项卡的内容,其函数原型如下:
lv_obj_t*lv_label_create(lv_obj_t*parent)其参数为新标签对象,会挂到这颗对象子树下,位置、裁剪、滚动、显隐全部跟随父对象。返回值为新 Label 的对象指针。
这几种方式有什么不同?都有什么特点?分别适合用在什么场景下?
维度 方法一 裸切 方法二 动画 方法三 FLAG 方法四 摘挂 方法五 TabView 内存占用 中 中 最大 中 中 切换速度 最快 中等 极快 快 中等 动画效果 ❌ ✅ 丰富 ❌(可自制) ❌(可自制) ✅ 水平滑动 状态保持 ✅ ✅(false时) ✅ ✅ ✅ 公共元素复用 ❌ ❌ ✅ 最佳 ✅ ✅ 代码复杂度 低 低 中 高 低 适合层级导航 ✅ ✅ ✅ ✅ ❌ 适合平级导航 ❌ ❌ ✅ ❌ ✅ 最佳 页面数量上限 ~10 ~10 ~5 ~15 ~5 真实项目中很少只用一种方法,常见组合如下:
组合模式 说明 主框架用 FLAG + 子页面用动画 顶部标题栏/底部导航栏用 FLAG 常驻,点击导航后内容区用 lv_scr_load_anim切换登录流程用裸切 + 主界面用动画 登录→主页用 lv_scr_load(单向、不需返回),主页内各功能页用lv_scr_load_anim(需要动画)TabView + FLAG TabView 管理顶层并列导航,每个 tab 内部有多个子页面用 FLAG 切换 LVGL中的动画系统是如何实现的?
LVGL 的动画系统是一个纯软件、时间驱动、回调执行的调度器。
它不依赖硬件加速,也不绑定帧率,而是靠lv_tick时间戳 + 内部定时器,周期性计算“当前时刻应该处于什么值”,再通过exec_cb把这个值写回对象。
理解了这个模型,就能解释前面遇到的跳变、卡顿以及 函数lv_scr_load_anim()到底做了什么。
下面给出一个简单的界面切换示例,使用的是第二种方法,这是项目中最常用的方法,也是LVGL官方推荐的方法,其代码如下:
*====================全局变量:两个屏幕对象====================*/staticlv_obj_t*screen1=NULL;staticlv_obj_t*screen2=NULL;voidlv_mainstart(void){/* 1. 创建两个屏幕(不显示,仅在内存中准备) */create_screen1();create_screen2();/* 2. 默认加载 screen1 作为初始画面 */lv_scr_load(screen1);}/* ==================== 创建屏幕 1 ==================== */staticvoidcreate_screen1(void){/* 创建屏幕(父对象为 NULL,表示它是顶层屏幕) */screen1=lv_obj_create(NULL);lv_obj_set_style_bg_color(screen1,lv_color_hex(0x1B3A4B),0);// 深蓝背景/* 标题标签 */lv_obj_t*title=lv_label_create(screen1);lv_label_set_text(title,"Screen 1");lv_obj_set_style_text_color(title,lv_color_white(),0);lv_obj_align(title,LV_ALIGN_CENTER,0,-60);/* 创建切换按钮 */lv_obj_t*btn=lv_btn_create(screen1);lv_obj_set_size(btn,150,50);lv_obj_align(btn,LV_ALIGN_CENTER,0,20);/* 按钮文字 */lv_obj_t*btn_label=lv_label_create(btn);lv_label_set_text(btn_label,"Go to Screen 2");lv_obj_align(btn_label,LV_ALIGN_CENTER,0,0);/* 绑定点击事件 */lv_obj_add_event_cb(btn,btn_click_event_cb,LV_EVENT_CLICKED,screen2);}/* ==================== 创建屏幕 2 ==================== */staticvoidcreate_screen2(void){screen2=lv_obj_create(NULL);lv_obj_set_style_bg_color(screen2,lv_color_hex(0x4B1B3A),0);// 深红背景/* 标题标签 */lv_obj_t*title=lv_label_create(screen2);lv_label_set_text(title,"Screen 2");lv_obj_set_style_text_color(title,lv_color_white(),0);lv_obj_align(title,LV_ALIGN_CENTER,0,-60);/* 创建切换按钮 */lv_obj_t*btn=lv_btn_create(screen2);lv_obj_set_size(btn,150,50);lv_obj_align(btn,LV_ALIGN_CENTER,0,20);/* 按钮文字 */lv_obj_t*btn_label=lv_label_create(btn);lv_label_set_text(btn_label,"Back to Screen 1");lv_obj_align(btn_label,LV_ALIGN_CENTER,0,0);/* 绑定点击事件 */lv_obj_add_event_cb(btn,btn_click_event_cb,LV_EVENT_CLICKED,screen1);}/* ==================== 按键点击事件回调 ==================== */staticvoidbtn_click_event_cb(lv_event_t*e){/* 1. 获取当前正在显示的屏幕 */lv_obj_t*current_scr=lv_scr_act();lv_obj_t*target_scr=NULL;/* 2. 动态判断:当前是哪个屏幕,就切换到另一个 */if(current_scr==screen1){target_scr=screen2;}else{target_scr=screen1;}/* 动画参数: * LV_SCR_LOAD_ANIM_MOVE_RIGHT :从右向左滑入(新屏幕从右侧推入) * 500 : 动画持续时间 500ms * 0 : 无延迟 * false: 切换后不删除旧屏幕(保留以便下次切回来) */lv_scr_load_anim(target_scr,LV_SCR_LOAD_ANIM_MOVE_RIGHT,500,0,false);}示例实验结果如下:
界面切换