)
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*Screen1lv_obj_create(lv_scr_act());lv_obj_t*Screen2lv_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*tabviewlv_tabview_create(lv_scr_act(),LV_DIR_TOP,50);lv_obj_t*tab1lv_tabview_add_tab(tabview,设置);lv_obj_t*tab2lv_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 FLAGTabView 管理顶层并列导航每个 tab 内部有多个子页面用 FLAG 切换LVGL中的动画系统是如何实现的LVGL 的动画系统是一个纯软件、时间驱动、回调执行的调度器。它不依赖硬件加速也不绑定帧率而是靠lv_tick时间戳 内部定时器周期性计算“当前时刻应该处于什么值”再通过exec_cb把这个值写回对象。理解了这个模型就能解释前面遇到的跳变、卡顿以及 函数lv_scr_load_anim()到底做了什么。下面给出一个简单的界面切换示例使用的是第二种方法这是项目中最常用的方法也是LVGL官方推荐的方法其代码如下*全局变量两个屏幕对象*/staticlv_obj_t*screen1NULL;staticlv_obj_t*screen2NULL;voidlv_mainstart(void){/* 1. 创建两个屏幕不显示仅在内存中准备 */create_screen1();create_screen2();/* 2. 默认加载 screen1 作为初始画面 */lv_scr_load(screen1);}/* 创建屏幕 1 */staticvoidcreate_screen1(void){/* 创建屏幕父对象为 NULL表示它是顶层屏幕 */screen1lv_obj_create(NULL);lv_obj_set_style_bg_color(screen1,lv_color_hex(0x1B3A4B),0);// 深蓝背景/* 标题标签 */lv_obj_t*titlelv_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*btnlv_btn_create(screen1);lv_obj_set_size(btn,150,50);lv_obj_align(btn,LV_ALIGN_CENTER,0,20);/* 按钮文字 */lv_obj_t*btn_labellv_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){screen2lv_obj_create(NULL);lv_obj_set_style_bg_color(screen2,lv_color_hex(0x4B1B3A),0);// 深红背景/* 标题标签 */lv_obj_t*titlelv_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*btnlv_btn_create(screen2);lv_obj_set_size(btn,150,50);lv_obj_align(btn,LV_ALIGN_CENTER,0,20);/* 按钮文字 */lv_obj_t*btn_labellv_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_scrlv_scr_act();lv_obj_t*target_scrNULL;/* 2. 动态判断当前是哪个屏幕就切换到另一个 */if(current_scrscreen1){target_scrscreen2;}else{target_scrscreen1;}/* 动画参数 * 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);}示例实验结果如下界面切换