LVGL版本:8.3
LVGL的控件各式各樣,每種控件都有自己的一些特性,當我們想要使用一個LVGL控件時,我們首先可以通過官網去了解控件的一些基本特性,官網鏈接如下:
LVGL Basics — LVGL documentation(LVGL官網)
Introduction — LVGL documentation(百問網)
在這里的“部件(Widgets)”一欄有關于各種控件的介紹:
? ? 但是當我們想要在代碼中實際使用控件時,分析該控件的源碼能讓我們對該控件的使用方法了解的更為透徹,這里就從我個人角度說明一下LVGL控件的特性以及一般的分析方法:
? ? 首先我們先從LVGL對象介紹開始,然后再拿彈窗控件舉例,看我們如何結合LVGL官網對于彈窗空間的介紹以及LVGL源碼中關于彈窗控件的描述,來實現編碼器作為輸入設備下點擊一個按鈕出現一個模態對話框,點擊關閉模態對話框后回到原頁面的功能;
LVGL對象介紹:
? ? 在LVGL中,用戶界面的基本構建塊是對象,也稱為Widgets。例如Button、Label、Image、List、圖表或文本區域。LVGL 中的所有控件(對象)都是基于 lv_obj_t 的。通過模塊化和面向對象設計,lv_obj_t 是 LVGL 中所有可視化對象的基類,它提供了對象的基本屬性和方法,如大小、位置、父子關系、樣式、事件回調等。每種控件都是 lv_obj_t 的派生類型,都直接或間接繼承自 lv_obj_t,通過擴展其基礎功能實現特定的控件功能。
typedef struct _lv_obj_t {const lv_obj_class_t * class_p;struct _lv_obj_t * parent;_lv_obj_spec_attr_t * spec_attr;_lv_obj_style_t * styles;
#if LV_USE_USER_DATAvoid * user_data;
#endiflv_area_t coords;lv_obj_flag_t flags;lv_state_t state;uint16_t layout_inv : 1;uint16_t readjust_scroll_after_layout : 1;uint16_t scr_layout_inv : 1;uint16_t skip_trans : 1;uint16_t style_cnt : 6;uint16_t h_layout : 1;uint16_t w_layout : 1;uint16_t being_deleted : 1;
} lv_obj_t;
屬性:“大小”和“位置”
? ? 關于對象屬性中的“大小”和“位置”很好理解,由于對象都可以理解為一個矩形,因此“大小”就是設置對象的寬和高,而位置分為絕對位置和相對位置,絕對位置就是對象在屏幕的x軸和y軸的坐標值,相對位置就是對象和另一個對象之間的位置關系,例如對象A在對象B左上方、下方等,如下圖:
屬性:“父子關系”
? ? 而“父子關系”就是對象的父對象和子對象,我們創建一個控件時都需要聲明該控件的父對象,例如按鈕控件創建函數lv_obj_t * lv_btn_create(lv_obj_t * parent)和標簽對象創建函數lv_obj_t * lv_label_create(lv_obj_t * parent)這種格式;“父子關系”這個屬性能夠幫助我們建立整個UI界面的對象樹從而讓對象擁有了繼承和層級的特性,極大地提升了 UI 組件的管理能力。
??“父子關系”屬性的繼承包括:位置繼承:子對象位置相對父對象,而不是屏幕;可見性繼承:父對象隱藏,所有子對象自動隱藏;樣式繼承:子對象繼承父對象的樣式;事件冒泡:事件可以從子對象傳遞給父對象;
? ?“父子關系”屬性的層級則可以控制不同控件在屏幕上重疊時誰顯示在前面誰顯示在后面,這里涉及到LVGL圖層的概念。LVGL將圖層分為三層,其中一個普通層act_scr和兩個特殊層top_layer和sys_layer,層和層之間的關系為:layer_top?始終位于默認屏幕 (?lv_scr_act()?)的最上方,?layer_sys?則始終位于?layer_top?的頂部?,通常用于系統級的界面元素。用戶可以使用?layer_top?來創建一些隨處可見的全局性的界面元素,例如彈出窗口、懸浮菜單等。使用?layer_sys顯示系統控件,例如鼠標指針、觸控反饋等。
? ? 同一個圖層內對象之間的關系為:默認情況下,在同一個父控件中后創建的控件會顯示在前面,即 "堆疊在上層"。例如我先在act_scr層創建了一個對象button1,又在該層的同樣位置創建了一個對象button2,那么button2堆疊會在button1上面,將button1“蓋住”;在同一個圖層內想要改變不同對象之間的層級關系,可以使用一些函數,例如對對象obj和new_parent使用函數lv_obj_set_parent(obj,new_parent)?時,將obj的父控件設置為new_parent,此時obj 將在?new_parent?的前面;或者使用lv_obj_move_foreground(obj) 將對象帶到當前圖層的最上面;類似地,使用?lv_obj_move_background(obj)?將對象 obj 移動到當前圖層的最下面。
自定義彈窗案例:
LVGL官網彈窗控件描述:
Message box (lv_msgbox) — LVGL documentation
LVGL源碼彈窗控件描述:
?大致工作邏輯如下:
1、如果 parent 為 NULL,就自動創建一個大小為整個屏幕的半透明“遮罩層”作為父對象,該父對象位于lv_layer_top()層,用于覆蓋整個背景;標志位auto_parent == true;
2、創建主消息框對象,若 auto_parent == true
,則加上 LV_MSGBOX_FLAG_AUTO_PARENT
,后續調用彈窗刪除函數lv_msgbox_close(lv_obj_t * mbox)是則會刪除彈窗父對象“遮罩層”。使用 flex 布局,子項會自動排列(wrap 換行);
3、如果需要標題或關閉按鈕,就創建頂部 label
(標題)和右上角關閉按鈕;
4、創建內部的 content
容器用于顯示彈窗內容,如果彈窗內容 txt
非空,創建一個 label 并設置其為自動換行。
5、創建按鈕矩陣(btnmatrix),注意按鈕矩陣的btn_txts[]應該是一個以 NULL 結尾的字符串指針數組,且當數組元素內容為""時不認為該元素是一個有效的按鈕;
lv_msgbox.c:lv_obj_t * lv_msgbox_create(lv_obj_t * parent, const char * title, const char * txt, const char * btn_txts[],bool add_close_btn)
{LV_LOG_INFO("begin");bool auto_parent = false;if(parent == NULL) {auto_parent = true;parent = lv_obj_class_create_obj(&lv_msgbox_backdrop_class, lv_layer_top());LV_ASSERT_MALLOC(parent);lv_obj_class_init_obj(parent);lv_obj_clear_flag(parent, LV_OBJ_FLAG_IGNORE_LAYOUT);lv_obj_set_size(parent, LV_PCT(100), LV_PCT(100));}lv_obj_t * obj = lv_obj_class_create_obj(&lv_msgbox_class, parent);LV_ASSERT_MALLOC(obj);if(obj == NULL) return NULL;lv_obj_class_init_obj(obj);lv_msgbox_t * mbox = (lv_msgbox_t *)obj;if(auto_parent) lv_obj_add_flag(obj, LV_MSGBOX_FLAG_AUTO_PARENT);lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_WRAP);bool has_title = title && strlen(title) > 0;/*When a close button is required, we need the empty label as spacer to push the button to the right*/if(add_close_btn || has_title) {mbox->title = lv_label_create(obj);lv_label_set_text(mbox->title, has_title ? title : "");lv_label_set_long_mode(mbox->title, LV_LABEL_LONG_SCROLL_CIRCULAR);if(add_close_btn) lv_obj_set_flex_grow(mbox->title, 1);else lv_obj_set_width(mbox->title, LV_PCT(100));}if(add_close_btn) {mbox->close_btn = lv_btn_create(obj);lv_obj_set_ext_click_area(mbox->close_btn, LV_DPX(10));lv_obj_add_event_cb(mbox->close_btn, msgbox_close_click_event_cb, LV_EVENT_CLICKED, NULL);lv_obj_t * label = lv_label_create(mbox->close_btn);lv_label_set_text(label, LV_SYMBOL_CLOSE);const lv_font_t * font = lv_obj_get_style_text_font(mbox->close_btn, LV_PART_MAIN);lv_coord_t close_btn_size = lv_font_get_line_height(font) + LV_DPX(10);lv_obj_set_size(mbox->close_btn, close_btn_size, close_btn_size);lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);}mbox->content = lv_obj_class_create_obj(&lv_msgbox_content_class, obj);LV_ASSERT_MALLOC(mbox->content);if(mbox->content == NULL) return NULL;lv_obj_class_init_obj(mbox->content);bool has_txt = txt && strlen(txt) > 0;if(has_txt) {mbox->text = lv_label_create(mbox->content);lv_label_set_text(mbox->text, txt);lv_label_set_long_mode(mbox->text, LV_LABEL_LONG_WRAP);lv_obj_set_width(mbox->text, lv_pct(100));}if(btn_txts) {mbox->btns = lv_btnmatrix_create(obj);lv_btnmatrix_set_map(mbox->btns, btn_txts);lv_btnmatrix_set_btn_ctrl_all(mbox->btns, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT);uint32_t btn_cnt = 0;while(btn_txts[btn_cnt] && btn_txts[btn_cnt][0] != '\0') {btn_cnt++;}const lv_font_t * font = lv_obj_get_style_text_font(mbox->btns, LV_PART_ITEMS);lv_coord_t btn_h = lv_font_get_line_height(font) + LV_DPI_DEF / 10;lv_obj_set_size(mbox->btns, btn_cnt * (2 * LV_DPI_DEF / 3), btn_h);lv_obj_set_style_max_width(mbox->btns, lv_pct(100), 0);lv_obj_add_flag(mbox->btns, LV_OBJ_FLAG_EVENT_BUBBLE); /*To see the event directly on the message box*/}return obj;
}void lv_msgbox_close(lv_obj_t * mbox)
{if(lv_obj_has_flag(mbox, LV_MSGBOX_FLAG_AUTO_PARENT)) lv_obj_del(lv_obj_get_parent(mbox));else lv_obj_del(mbox);
}static void msgbox_close_click_event_cb(lv_event_t * e)
{lv_obj_t * btn = lv_event_get_target(e);lv_obj_t * mbox = lv_obj_get_parent(btn);lv_msgbox_close(mbox);
}
實際實現:
? ? 編碼器作為輸入設備的情況下,點擊一個按鈕會出現一個模態對話框彈窗,彈窗中有一個按鈕組,按鈕組中一個用于關閉彈窗的按鈕,點擊關閉按鈕模態對話框會消失然回到原頁面。同時可以選擇彈窗的樣式,分為ERROR和正常兩種樣式以適應不同類型的彈窗;
巧用lvgl的圖層(layer)編寫模態對話框 - LVGL - 嵌入式開發問答社區
? ??首先我們根據我們上面獲取到的關于彈窗的信息我們可以發現,LVGL官網中說明了彈窗這個控件可以為模態和非模態兩種,同時可以為彈窗設置標題和文本以及一個按鈕組,同時彈窗右上角有一個可選的關閉按鈕,該按鈕的功能固定為關閉彈窗;
? ? 我們從LVGL源碼中可以發現彈窗控件的創建函數lv_msgbox_create(lv_obj_t * parent, const char * title, const char * txt, const char * btn_txts[],bool add_close_btn)是如何創建彈窗的,通過源碼我們印證了官網中關于彈窗的一些使用描述,同時對該控件有了更深層次的理解;
? ? ?下面來講一下我是如何實現上述功能的,首先我需要在觸發彈窗按鈕的EVENT事件回調函數中調用觸發模態彈窗的函數,由于我的輸入設備是編碼器模式,因此控件焦點的切換是依據group組來實現,為了真正實現模態的效果,我需要在彈窗出現之后新建一個臨時的group組并將其設置為默認組(創建控件時,控件中可交互的部分會自動加入默認的group組,而無需我們手動添加,很省事),在彈窗關閉后刪除該臨時group組并將原先的group組恢復為默認組;觸發模態彈窗的函數需要傳入一個類型參數以便更改不同彈窗樣式;
? ? ?這里由于彈窗的可選關閉按鈕只有關閉彈窗功能,因此這里我選擇將彈窗的按鈕組中的按鈕作為關閉彈窗按鈕,這樣不僅能關閉彈窗還能滿足我更換group默認組的功能,最后的實現如下:
User_msgbox.h:
#ifndef _LVGL_OPERATION_H_
#define _LVGL_OPERATION_H_#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lvgl.h"typedef struct{lv_group_t *Original_group; //正常組lv_group_t *msgbox_group; //彈窗專用組lv_indev_t *Original_indev; //輸入設備lv_obj_t *mask_obj; //彈窗父對象“遮罩層”bool is_active; //防止彈窗重復觸發uint8_t ERROR_Mode; //錯誤模式(0表示正常,1表示錯誤)char* Text;
}User_msgbox_t;lv_obj_t* create_User_msgbox(char* Text, uint8_t ERROR_Mode, lv_group_t *Original_group, lv_indev_t *Original_indev);
void User_msgbox_event_cb(lv_event_t * e);
#endifUser_msgbox.c:
//***** 彈窗定義 *****//
/* 正常風格樣式對象 */
lv_style_t msgbox_main_style_normal;
lv_style_t msgbox_title_style_normal;
lv_style_t msgbox_text_style;
lv_style_t msgbox_btns_style;/* 錯誤風格樣式對象 */
lv_style_t msgbox_main_style_error;
lv_style_t msgbox_title_style_error;void init_common_msgbox_styles(void) {/* ---------------- 正常風格 ---------------- */lv_style_init(&msgbox_main_style_normal);lv_style_set_bg_opa(&msgbox_main_style_normal, 255);lv_style_set_bg_color(&msgbox_main_style_normal, lv_color_hex(0xffffff));lv_style_set_border_width(&msgbox_main_style_normal, 4);lv_style_set_border_opa(&msgbox_main_style_normal, 255);lv_style_set_border_color(&msgbox_main_style_normal, lv_color_hex(0x2195F6)); // 藍色邊框lv_style_set_border_side(&msgbox_main_style_normal, LV_BORDER_SIDE_FULL);lv_style_set_radius(&msgbox_main_style_normal, 4);lv_style_set_shadow_width(&msgbox_main_style_normal, 0);lv_style_init(&msgbox_title_style_normal);lv_style_set_text_color(&msgbox_title_style_normal, lv_color_hex(0x000000));lv_style_set_text_font(&msgbox_title_style_normal, &lv_font_SourceHanSerifSC_Regular_15);lv_style_set_text_opa(&msgbox_title_style_normal, 255);lv_style_set_text_letter_space(&msgbox_title_style_normal, 0);lv_style_set_text_line_space(&msgbox_title_style_normal, 30);/* 內容和按鈕的樣式可以共用 */lv_style_init(&msgbox_text_style);lv_style_set_text_color(&msgbox_text_style, lv_color_hex(0x000000));lv_style_set_text_font(&msgbox_text_style, &lv_font_SourceHanSerifSC_Regular_15);lv_style_set_text_opa(&msgbox_text_style, 255);lv_style_set_text_letter_space(&msgbox_text_style, 0);lv_style_set_text_line_space(&msgbox_text_style, 10);lv_style_init(&msgbox_btns_style);lv_style_set_bg_opa(&msgbox_btns_style, 255);lv_style_set_bg_color(&msgbox_btns_style, lv_color_hex(0x2195F6));lv_style_set_bg_grad_dir(&msgbox_btns_style, LV_GRAD_DIR_NONE);lv_style_set_border_width(&msgbox_btns_style, 0);lv_style_set_radius(&msgbox_btns_style, 10);lv_style_set_text_color(&msgbox_btns_style, lv_color_hex(0x000000));lv_style_set_text_font(&msgbox_btns_style, &lv_font_SourceHanSerifSC_Regular_15);lv_style_set_text_opa(&msgbox_btns_style, 255);/* ---------------- 錯誤風格 ---------------- */lv_style_init(&msgbox_main_style_error);lv_style_set_bg_opa(&msgbox_main_style_error, 255);lv_style_set_bg_color(&msgbox_main_style_error, lv_color_hex(0xffffff));lv_style_set_border_width(&msgbox_main_style_error, 4);lv_style_set_border_opa(&msgbox_main_style_error, 255);lv_style_set_border_color(&msgbox_main_style_error, lv_color_hex(0xff0000)); // 紅色邊框lv_style_set_border_side(&msgbox_main_style_error, LV_BORDER_SIDE_FULL);lv_style_set_radius(&msgbox_main_style_error, 4);lv_style_set_shadow_width(&msgbox_main_style_error, 0);lv_style_init(&msgbox_title_style_error);lv_style_set_text_color(&msgbox_title_style_error, lv_color_hex(0xff0000)); // 紅色文字lv_style_set_text_font(&msgbox_title_style_error, &lv_font_SourceHanSerifSC_Regular_15);lv_style_set_text_opa(&msgbox_title_style_error, 255);lv_style_set_text_letter_space(&msgbox_title_style_error, 0);lv_style_set_text_line_space(&msgbox_title_style_error, 30);
}/* 應用正常風格到消息框 */
void apply_normal_msgbox_styles(lv_obj_t *msgbox) {lv_obj_add_style(msgbox, &msgbox_main_style_normal, LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_add_style(lv_msgbox_get_title(msgbox), &msgbox_title_style_normal, LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_add_style(lv_msgbox_get_text(msgbox), &msgbox_text_style, LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_add_style(lv_msgbox_get_btns(msgbox), &msgbox_btns_style, LV_PART_ITEMS | LV_STATE_DEFAULT);
}/* 應用錯誤風格到消息框 */
void apply_error_msgbox_styles(lv_obj_t *msgbox) {lv_obj_add_style(msgbox, &msgbox_main_style_error, LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_add_style(lv_msgbox_get_title(msgbox), &msgbox_title_style_error, LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_add_style(lv_msgbox_get_text(msgbox), &msgbox_text_style, LV_PART_MAIN | LV_STATE_DEFAULT);lv_obj_add_style(lv_msgbox_get_btns(msgbox), &msgbox_btns_style, LV_PART_ITEMS | LV_STATE_DEFAULT);
}static void create_User_msgbox_cb(lv_timer_t * timer)
{lv_group_set_default(((User_msgbox_t*)(timer->user_data))->msgbox_group); //將該組設置為默認組lv_indev_set_group(((User_msgbox_t*)(timer->user_data))->Original_indev, ((User_msgbox_t*)(timer->user_data))->msgbox_group); // 綁定編碼器輸入設備至該組init_common_msgbox_styles(); // 初始化消息框樣式static const char * btns[] = {"Close",NULL}; //根據要求,按鈕組需要以NULL結尾lv_obj_t * msgbox ;if(((User_msgbox_t*)(timer->user_data))->ERROR_Mode == 0){msgbox = lv_msgbox_create(NULL, (const char*)"Tip", ((User_msgbox_t*)(timer->user_data)) -> Text, btns, false);}else{msgbox = lv_msgbox_create(NULL, (const char*)"ERROR", ((User_msgbox_t*)(timer->user_data)) -> Text, btns, false); }// 這里可以設置消息框的位置、大小等lv_obj_set_pos(msgbox, 28, 55);lv_obj_set_size(msgbox, 248, 130);if(((User_msgbox_t*)(timer->user_data))->ERROR_Mode == 0 ){apply_normal_msgbox_styles(msgbox); // 應用正常風格}else{apply_error_msgbox_styles(msgbox); // 應用錯誤風格} lv_obj_add_event_cb(msgbox, User_msgbox_event_cb, LV_EVENT_CLICKED, timer->user_data);lv_timer_del(timer); // 刪除定時器自身
}/*** @brief 創建一個用戶自定義的消息框* * @param Text 消息框的文本內容* @param ERROR_Mode 錯誤模式(0表示正常,1表示錯誤)* @param Original_group 原始的組對象* @param Original_indev 原始的輸入設備對象* @return lv_obj_t* 返回創建的消息框對象
*/
uint8_t create_User_msgbox(char* Text, uint8_t ERROR_Mode, lv_group_t *Original_group, lv_indev_t *Original_indev)
{static User_msgbox_t User_msgbox = {0};if (User_msgbox.is_active) return 0;User_msgbox.is_active = true; // 標記彈窗已創建,防止重復響應lv_group_t* msgbox_group = lv_group_create(); //創建臨時組User_msgbox.msgbox_group = msgbox_group;User_msgbox.Original_group = Original_group;User_msgbox.Original_indev = Original_indev;User_msgbox.ERROR_Mode = ERROR_Mode;User_msgbox.Text = Text;// 延遲創建消息框lv_timer_t * del_timer = lv_timer_create(create_User_msgbox_cb, 300, &User_msgbox); return 1;
}static void delete_obj_cb(lv_timer_t * timer)
{// 在關閉前執行恢復操作,比如恢復原先的groupif(((User_msgbox_t*)(timer->user_data)) != NULL) {lv_group_set_default(((User_msgbox_t*)(timer->user_data))->Original_group); //將該組設置為默認組lv_indev_set_group(((User_msgbox_t*)(timer->user_data))->Original_indev,((User_msgbox_t*)(timer->user_data))->Original_group);}lv_group_del(((User_msgbox_t*)(timer->user_data))->msgbox_group); // 刪除臨時的 grouplv_obj_del(((User_msgbox_t*)(timer->user_data))->mask_obj);lv_timer_del(timer); // 刪除定時器自身((User_msgbox_t*)(timer->user_data)) -> is_active = false;
}void User_msgbox_event_cb(lv_event_t * e)
{lv_event_code_t code = lv_event_get_code(e);lv_obj_t *target = lv_event_get_target(e); //這里獲取到的是按鈕組對象if(code == LV_EVENT_CLICKED) {((User_msgbox_t*)(e->user_data)) -> mask_obj = lv_obj_get_parent(lv_obj_get_parent(e->target)); // mask 是要刪除的對象// 延遲刪除遮罩(祖先對象)lv_timer_t * del_timer = lv_timer_create(delete_obj_cb, 15, e->user_data); // mask 是要刪除的對象}
}main.c:
static void Screen_event_handler (lv_event_t *e)
{lv_event_code_t code = lv_event_get_code(e); //獲取當前事件觸發的觸發類型lv_obj_t *target = lv_event_get_target(e); //獲取觸發該回調的控件switch (code) {case LV_EVENT_PRESSED:{ if(target == guider_ui.btnSave){lv_obj_t * msgbox = create_User_msgbox("標定數據保存成功", 0, Original_group, indev_encoder);}}break;default:break;}
}
? 踩坑記錄:
? ? ? 我們從上面可以看出來這里我彈窗的創建和刪除都使用了LVGL軟件定時器來實現異步延遲處理,至于為什么要這樣做就需要說一下我在使用編碼器設備實現自定義彈窗時遇到的一些坑;
? ? ? ?首先就是為什么要異步延遲創建彈窗?這時因為我發現當我在“觸發彈窗按鈕”中直接創建彈窗時,刪除彈窗后“觸發彈窗按鈕”的樣式一直為Focus狀態下的樣式無法改變,就算不聚焦在該按鈕控件上時也一樣,考慮到可能是在該按鈕的EVENT回調函數中修改了默認group組后,導致后續該按鈕的樣式渲染出現了問題,因此采用異步延時來等待該按鈕執行EVENT回調函數并渲染完新的狀態后再去修改默認group組以及創建彈窗,延時時間對結果的影響實測如下:
? ? 1、當延時時間為15ms時觸發創建彈窗再關閉彈窗后按鈕樣式一定有問題;
? ? 2、延時時間為100ms時觸發創建彈窗再關閉彈窗后按鈕樣式有時候有問題,有時候正常;
? ? 3、延時時間為200ms時觸發創建彈窗再關閉彈窗后按鈕樣式一直正常;
? ? ?其次就是為什么要異步延遲刪除彈窗?這時因為實測中在彈窗的按鈕組按鈕EVENT事件回調函數中用lv_obj_del(obj)
刪除彈窗父控件“遮罩層”是偶爾出現卡死現象,去網上查詢后說需要使用lv_obj_del_async(obj)
函數異步刪除控件更安全,他們的區別如下:
lv_obj_del(obj)
—— 立即刪除對象:直接釋放對象和所有子對象;立刻從內存中移除;如果此時對象正在使用(比如在事件回調中),就可能導致訪問野指針 → 程序崩潰(卡死)!有時候崩潰有時候沒崩潰的原因:有時你運氣好,事件系統剛處理完,不再訪問對象 → 沒崩,有時你運氣不好,還在訪問它,就讀了非法內存 → 崩潰(卡死)
? ? 使用時機:不要在對象的事件回調中對自己或自己的 parent 使用它;適合在沒有事件相關聯或生命周期明確的情況下使用。
lv_obj_del_async(obj)
—— 延遲刪除對象:標記對象為“待刪除”,在下一個 LVGL 刷新周期中再真正刪除;安全地用于事件回調內部;避免因“正在使用又刪除自己”而引起的訪問非法內存。
? ? 推薦場景:在 LV_EVENT_CLICKED
、LV_EVENT_PRESSED
等事件中想刪除當前消息框、按鈕、parent 時;復雜對象之間有事件鏈、動畫等未結束的交互時;想刪除帶動畫的控件(刪除前動畫未完成也沒關系)。建議 除非明確知道對象未被使用,否則都優先用 lv_obj_del_async()
? ? 但問題是實際使用時lv_obj_del_async(obj)
刪除彈窗時效果更差,每次都卡死,看了如下兩篇文章也沒找到原因:
進行刪除控件時候,代碼崩潰 - LVGL - 嵌入式開發問答社區
調用 lv_obj_del() 或 lv_obj_del_async 時 _lv_event_mark_deleted() 崩潰 ·問題 #6035 ·LVGL/LVGL
? ?因此這里我就使用定時器異步延時+lv_obj_del(obj)
的方式去刪除彈窗,這樣更穩妥,實際使用也沒遇到卡死現象了;
? ? 結論:不要在控件回調函數中刪除本控件及其父控件,也不要修改group組的默認組,這些操作應該用定時器延時異步實現,延時時間視實際情況而定;