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组的默认组,这些操作应该用定时器延时异步实现,延时时间视实际情况而定;