LVGL learning (3): page switching principle and page management implementation

In LVGL, there are multiple pages in most cases. Generally speaking, there are two situations for page switching:

  1. Delete the current page, create a new page to load
  2. Keep the current page, create a new page to load

Let's analyze these two situations. For example, page 1 has a list box with three options, and each option corresponds to a page. Assuming that our focus falls on the second option at this time, and then click to enter page 2, if we want to return, do we want the focus to remain on the second option in the list. Based on this situation, we hope to be able to create and load page 2 while retaining page 1 (while also retaining the focus). At the same time, for page 2, we only want to load it when the user chooses, and it is in the initial state every time it enters, so when we return to page 1 from page 2, we want to delete page 2, and then create page 1 and load it.

So we need to understand these two switching methods. Let's take a look at how to complete page switching in LVGL.

1 page switching function

First, in either case, we need to create a new page to load. In LVGL, a function is used lv_scr_loadto load a new page, and its calling relationship is as follows:

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

Here we do not lv_scr_load_animconduct an in-depth analysis, we only need to know that this function can eventually be used to display a page, which animmeans animation, that is, there can be some animation when the page is loaded. lv_scr_loadThe function loads the page directly without any animation. So we can also use it directly lv_scr_load_animto load the page.

With this function, page switching is actually very simple. For each page, a lv_obj_tbasic object represents a page. We only need to make all components use this page lv_obj_tas the parent class, and then use lv_scr_load_animthe function to load That's it.

Hidden page switching method?
The hidden page switching method is to call lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN)and lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN)to hide/display the page to realize the page switching. If you want to display multiple pages together, that is, there is a cascading relationship, you can specify that all pages have a common parent class.
But for lv_scr_load_animthe function, it only supports the display of a single page, which page is displayed as the parameter, even if the previous page has not been deleted, it will not be displayed. So in my opinion, there is no so-called hidden page switching method.

2 Conditions and reasons for page switching

For the actual page switching, we also need to consider some things, such as switching from the current page, whether to delete or retain the current page and all its sub-objects. Let's take the code generated by GUI Guider as an example to see how it deletes pages and calls to switch lv_scr_load_animpages. After all, the code generated on this software must be very rigorous.


Let me add a word here, a lot of information on the Internet is not very rigorous. I downloaded someone else's LVGL source code for learning, and it is no problem to use the page switching code inside to run in the microcontroller, but it may crash in Codeblocks simulation. This is not that CodeBlocks is not compatible, but that the code itself has the problem of memory out-of-bounds access. This reminded me that the code I wrote with QT ran normally on Windows, but it crashed immediately when it arrived on Linux, which was actually caused by accessing an empty memory.

For example, BootLoader, if a certain peripheral interrupt, Cache, GPIO clock, etc. are enabled, it must be turned off before exiting BootLoader, that is, the previous state is restored. I have personally experienced that my colleague's BootLoader did not close the serial port interrupt before exiting, and then a certain bit of a constant string was inexplicably modified after entering the APP, and then asked me why. A lot of content on the Internet is not rigorous, and it may be usable on the surface, but it also leaves some potential mistakes. My suggestion is to refer to the code with a lot of Stars in the official SDK or Github.


As shown in the figure below, we first create two pages:
insert image description here
we want to switch to page 2 after the button on page 1 is pressed, and switch to page 1 after the button on page 2 is pressed. Select the button in the GUI Guide, and add Events on the right, as shown in the figure below, the Events of page 1 is on the left, and the Events of page 2 is on the right.
insert image description here
Take page 1 as an example, the figure above shows that after the button is pressed, page 2 will be loaded. In addition, we noticed that in the GUI Guider, there is another Delete current screenoption, and another Free memory of current screen before loading new screenoption appears after selecting it. In three cases, the code to switch pages after clicking the button is as follows:

/* 未选中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_2It can be seen that if the page to be loaded is set to be deleted, it needs to be called to reinitialize the components in the page before loading a new page each time, and then lv_scr_load_animthe last parameter auto_delis set to true, indicating that the original page is automatically deleted after loading objects in . If you want to release the content in the original page before loading the new page, lv_obj_cleanjust call it.

Now let’s analyze the common features of the above code:
(1) : Obtain the pointer of the type of lv_obj_t * act_scr = lv_scr_act()page being displayed on the current screen (2) : Obtain the display object associated with the current screen object (3) LVGL supports page switching animation, such as from left to right Switch to the right, just for this. When not , it means that the current screen object is switching animation, and it can be referenced to the previous screen object. This is to prevent the deletion of the previous screen object during the execution of the animation, and to avoid possible resource conflicts or inconsistencies ( 4 ) The page can only be switched when it is equal to or . Indicates that the animation for switching pages is not currently being executed (it has been executed), and indicates that it is currently in the process of switching from another page to the current page. In other cases, such as when the current page 1 ( ) is about to switch to page 2, switching to page 3 is not allowed. (5) means to delete all sub-objects of the current page before loading a new page. But when the last parameter of is true, it will be called to delete the previous page and its sub-objects the next time the screen is switched. But in fact, if we delete its sub-objects first, it will not affect the animation switching effect, because the sub-objects have already been drawn in , and we only need one display state of the current page to complete the animation transition effect.lv_obj_t
lv_disp_t * d = lv_obj_get_disp(act_scr);
d->prev_scr == NULL
prev_scrprev_scrNULLprev_scrprev_scrNULL
(d->scr_to_load == NULL || d->scr_to_load == act_scr)
scr_to_load NULLact_scrNULLact_scract_scr
lv_obj_clean
lv_scr_load_animlv_obj_delact_scr

Here I just describe the execution process of the function in words, please refer to the specific principle lv_disp.c. If necessary, I will write an article on source code analysis.

3 Modular implementation of page switching

From the above, we can find that, in fact, page switching is to call those lines of code, but it feels a bit redundant to write those lines in the callback function of each page, so we simply write a page switching function to manage all pages Get up, call the function to switch between each other.
(1) Define the page structure PageStruct_t and the page identifier 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) Adding and creating pages
A linked list is implemented here to manage each interface. Linked list implementation is very common and the code volume is very long, so the code will not be posted. page_findand are list_rpushlinked list related functions.

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) Page switching
Page switching is actually the query operation of the linked list added on the basis of the code generated by the GUI Guide just now. Here, when it is 1, it is called to clean up the sub-objects of the current page delbefore switching pages by default .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) Example of adding LOGO page

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

Guess you like

Origin blog.csdn.net/tilblackout/article/details/131058317