LVGL源碼(9):學會控件的使用(自定義彈窗)

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_CLICKEDLV_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組的默認組,這些操作應該用定時器延時異步實現,延時時間視實際情況而定;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/902234.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902234.shtml
英文地址,請注明出處:http://en.pswp.cn/news/902234.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《軟件設計師》復習筆記(1)——考試介紹【新】

目錄 一、考試介紹 證書價值 考試要求 二、【新】計算機與軟件工程知識 三、軟件設計 一、考試介紹 >考試科目>考題形式>考試時長>合格標準計算機與軟件工程知識75道單選題(每題1分,總分75分)2023年11月改革機試后&#…

MCU中的BSS和data都占用SRAM空間嗎?

在MCU中,BSS段和data段都占用SRAM空間,但它們的存儲方式和用途有所不同。? BSS段 BSS段(Block Started by Symbol)用于存儲未初始化的全局變量和靜態變量。這些變量在程序啟動時會被清零,因此它們不占用Flash空間&a…

Ubuntu 22.04 更換 Nvidia 顯卡后啟動無法進入桌面問題的解決

原顯卡為 R7 240, 更換為 3060Ti 后, 開機進桌面時卡在了黑屏界面, 鍵盤有反應, 但是無法進入 shell. 解決方案為 https://askubuntu.com/questions/1538108/cant-install-rtx-4060-ti-on-ubuntu-22-04-lts 啟動后在開機菜單中(如果沒有開機菜單, 需要按shift鍵), 進入recove…

Python爬蟲-爬取貓眼演出數據

前言 本文是該專欄的第53篇,后面會持續分享python爬蟲干貨知識,記得關注。 貓眼平臺除了有影院信息之外,它還涵蓋了演出信息,比如說“演唱會,音樂節,話劇音樂劇,脫口秀,音樂會,戲曲藝術,相聲”等等各種演出相關信息。 而本文,筆者將以貓眼平臺為例,基于Python爬蟲…

人工智能-機器學習(線性回歸,邏輯回歸,聚類)

人工智能概述 人工智能分為:符號學習,機器學習。 機器學習是實現人工智能的一種方法,深度學習是實現機器學習的一種技術。 機器學習:使用算法來解析數據,從中學習,然后對真實世界中是事務進行決策和預測。如垃圾郵件檢…

FPGA學習(五)——DDS信號發生器設計

FPGA學習(五)——DDS信號發生器設計 目錄 FPGA學習(五)——DDS信號發生器設計一、FPGA開發中常用IP核——ROM/RAM/FIFO1、ROM簡介2、ROM文件的設置(1)直接編輯法(2)用C語言等軟件生成初始化文件 3、ROM IP核配置調用 二、DDS信號發…

【Vue】從 MVC 到 MVVM:前端架構演變與 Vue 的實踐之路

個人博客:haichenyi.com。感謝關注 一. 目錄 一–目錄二–架構模式的演變背景?三–MVC:經典的分層起點?四–MVP:面向接口的解耦嘗試?五–MVVM:數據驅動的終極形態??六–Vue:MVVM 的現代化實踐??? 二. 架構模…

【算法】快速排序、歸并排序(非遞歸版)

目錄 一、快速排序&#xff08;非遞歸&#xff09; 1.原理 2.實現 2.1 stack 2.2 partition(array,left,right) 2.3 pivot - 1 > left 二、歸并排序&#xff08;非遞歸&#xff09; 1.原理 2.實現 2.1 gap 2.1.1 i 2*gap 2.1.2 gap * 2 2.1.3 gap < array.…

CasualLanguage Model和Seq2Seq模型的區別

**問題1&#xff1a;**Causal Language Modeling 和 Conditional Generation 、Sequence Classification 的區別是什么&#xff1f; 因果語言模型(Causal Language Model)&#xff1a; 預測給定文本序列中的下一個字符&#xff0c;一般用于文本生成、補全句子等&#xff0c;模型…

【計算機視覺】三維視覺項目 - Colmap二維圖像重建三維場景

COLMAP 3D重建 項目概述項目功能項目運行方式1. 環境準備2. 編譯 COLMAP3. 數據準備4. 運行 COLMAP 常見問題及解決方法1. **編譯問題**2. **運行問題**3. **數據問題** 項目實戰建議項目參考文獻 項目概述 COLMAP 是一個開源的三維重建軟件&#xff0c;專注于 Structure-from…

狀態管理最佳實踐:Bloc架構實踐

狀態管理最佳實踐&#xff1a;Bloc架構實踐 引言 Bloc (Business Logic Component) 是Flutter中一種強大的狀態管理解決方案&#xff0c;它基于響應式編程思想&#xff0c;通過分離業務邏輯和UI表現層來實現清晰的代碼架構。本文將深入探討Bloc的核心概念、實現原理和最佳實踐…

Python多任務編程:進程全面詳解與實戰指南

1. 進程基礎概念 1.1 什么是進程&#xff1f; 進程(Process)是指正在執行的程序&#xff0c;是程序執行過程中的一次指令、數據集等的集合。簡單來說&#xff0c;進程就是程序的一次執行過程&#xff0c;它是一個動態的概念。 想象你打開電腦上的音樂播放器聽歌&#xff0c;…

Linux 網絡基礎(二) (傳輸協議層:UDP、TCP)

目錄 一、傳輸層的意義 二、端口號 1、五元組標識一個通信 2、端口號范圍劃分 3、知名端口號&#xff08;Well-Know Port Number&#xff09; &#xff08;1&#xff09;查看端口號 4、綁定端口號數目問題 5、pidof & netstat 命令 &#xff08;1&#xff09;ne…

得佳勝哲訊科技 SAP項目啟動會:膠帶智造新起點 數字轉型新征程

在全球制造業加速向數字化、智能化轉型的浪潮中&#xff0c;膠帶制造行業正迎來以“自動化生產、數據化運營、智能化決策”為核心的新變革。工業互聯網、大數據分析與智能裝備的深度融合&#xff0c;正推動膠帶制造從傳統生產模式向“柔性化生產精準質量控制全鏈路追溯”的智慧…

大數據學習棧記——MapReduce技術

本文介紹hadoop中的MapReduce技術的應用&#xff0c;使用java API。操作系統&#xff1a;Ubuntu24.04。 MapReduce概述 MapReduce概念 MapReduce是一個分布式運算程序的編程框架&#xff0c;核心功能是將用戶編寫的業務邏輯代碼和自帶默認組件整合成一個完整的分布式運算程序…

Centos9 離線安裝 MYSQL8

centos 9 離線安裝 mysql 8 參考教程 1. 官網下載mysql 下載地址 2. 將文件傳輸到Centos中解壓 軟件全部安裝到了/opt中 在opt中新建mysql目錄&#xff0c;解壓到mysql目錄中 tar -xvf mysql壓縮文件 mysql[rootcentoshost mysql]# ls mysql-community-client-8.4.5-1.e…

helm的go模板語法學習

1、helm chart 1.0、什么是helm&#xff1f; 介紹&#xff1a;就是個包管理器。理解為java的maven、linux的yum就好。 安裝方法也可參見官網&#xff1a; https://helm.sh/docs/intro/install 通過前面的演示我們知道&#xff0c;有了helm之后應用的安裝、升級、查看、停止都…

display的一些學習記錄

收集的SDM的log&#xff1a; 01-01 00:00:15.311 933 933 I SDM : Creating Display HW Composer HAL 01-01 00:00:15.311 933 933 I SDM : Scheduler priority settings completed 01-01 00:00:15.311 933 933 I SDM : Configuring RPC threadpool 0…

【Rust 精進之路之第2篇-初體驗】安裝、配置與 Hello Cargo:踏出 Rust 開發第一步

系列&#xff1a; Rust 精進之路&#xff1a;構建可靠、高效軟件的底層邏輯 **作者&#xff1a;**碼覺客 發布日期&#xff1a; 2025-04-20 引言&#xff1a;磨刀不誤砍柴工&#xff0c;裝備先行&#xff01; 在上一篇文章中&#xff0c;我們一起探索了 Rust 誕生的緣由&…

【深度學習】計算機視覺(17)——ViT理解與應用

文章目錄 Embedding1 概念2 Q&A &#xff08;1&#xff09;3 Positional Encoding4 Q&A &#xff08;2&#xff09; ViT樣例及Embedding可視化理解1 簡化ViT練習2 CLS Token3 Embedding可視化4 多頭注意力可視化 Embedding技術體系結構參考來源 在研究中對特征的編碼和…