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_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组的默认组,这些操作应该用定时器延时异步实现,延时时间视实际情况而定;
相关文章:
LVGL源码(9):学会控件的使用(自定义弹窗)
LVGL版本:8.3 LVGL的控件各式各样,每种控件都有自己的一些特性,当我们想要使用一个LVGL控件时,我们首先可以通过官网去了解控件的一些基本特性,官网链接如下: LVGL Basics — LVGL documentation…...
HarmonyOs学习 环境配置后 实验1:创建项目Hello World
HarmonyOS开发入门:环境配置与Hello World实验 实验目标 掌握HarmonyOS开发环境配置,创建首个HarmonyOS应用并实现"Hello World"界面展示 实验准备 已安装DevEco Studio开发环境已配置HarmonyOS开发依赖项熟悉基本TypeScript/ArkTS语法&am…...
国产SMT贴片机自主技术突破解析
内容概要 随着电子信息产业对精密制造需求的持续升级,国产SMT贴片机的技术突破已成为装备自主化进程的关键节点。本文聚焦设备研发的三大核心领域:高动态运动控制系统通过线性电机与数字信号处理技术的融合,将重复定位精度提升至5μm级别&am…...
8、表单控制:预言水晶球——React 19 复杂表单处理
一、水晶球的预言本质 "每个表单都是时空裂缝中的预言容器,"占卜课教授特里劳妮凝视着水晶球,"React-Hook-Form与Formik的融合,让数据捕获如同捕捉未来碎片!" ——以魔法部神秘事务司的预言厅为隐喻…...
8 编程笔记全攻略:Markdown 语法精讲、Typora 编辑器全指南(含安装激活、基础配置、快捷键详解、使用技巧)
1 妙笔在手,编程无忧! 1.1 编程为啥要做笔记?这答案绝了! 嘿,各位键盘魔法师!学编程不记笔记,就像吃火锅不配冰可乐 —— 爽到一半直接噎住!你以为自己脑子是顶配 SSD,结…...
【MySQL】SQL语句在MySQL中的执行过程?主要存储引擎区别?
MySQL SQL语句执行过程详解 作为面试官,我来详细剖析一条SQL语句在MySQL中的完整执行过程,这是每个后端开发者都应该掌握的核心知识。 一、连接阶段 建立连接 客户端通过TCP/IP协议与MySQL服务器建立连接(默认3306端口)服务器验证用户名、密码和权限…...
Linux(autoDL云服务器)mamba-ssm环境安装——一次成功!
1.创建环境选择torch2.0, cuda11.8,python3.8 2.从GitHub官网下载cp38对应的,causl_conv1d,和mamba-ssm2.2.2。下载入下图所示。 3.直接用finalshell 或者xshell连接服务器上传,到根目录下面。 直接用pip install *…...
代码审计入门 原生态sql注入篇
前置知识: 漏洞形成的原因: 1、可控的参数 2、函数缺陷 代码审计的步骤: 1、全局使用正则搜索 漏洞函数 ,然后根据函数看变量是否可控,再看函数是否有过滤 2、根据web的功能点寻找函数,然后根据函数看…...
spring Ai---向量知识库(一)
在一些垂直领域以及公司内部信息相关或者实时性相关的大模型应用,就无法直接使用chatGPT。 这个时候,向量知识库就进入了。 通过坐标向量最接近的即为匹配相关答案。 向量模型定义:将文档向量化,保证内容越相似的文本,…...
jmeter利用csv进行参数化和自动断言
1.测试数据 csv测试数据如下(以注册接口为例) 2.jemer参数化csv设置 打开 jmeter,添加好线程组、HTTP信息头管理器、CSV 数据文件设置、注册请求、响应断言、查看结果树 1) CSV 数据文件设置 若 CSV 中数据包含中文,…...
C# 类型、存储和变量(数据成员和函数成员)
本章内容 C#程序是一组类型声明 类型是一种模板 实例化类型 数据成员和函数成员 预定义类型 用户定义类型 栈和堆 值类型和引用类型 变量 静态类型和dynamic关键字 可空类型 数据成员和函数成员 像short、int和long等这样的类型称为简单类型。这种类型只能存储一个数据项。 其…...
Java八种常见的设计模式
一、单例模式 单例模式是(Singleton Pattern)Java中最常用的设计模式之一,它保证一个类仅有一个实例,并提供一个全局访问点。 实现单例模式的核心是将类的构造方法私有化,以防止外部直接通过构造函数创建实例。同时&am…...
数据结构实验7.2:二叉树的基本运算
文章目录 一,实验目的二,问题描述三,基本要求四,实验操作五,示例代码六,运行效果 一,实验目的 深入理解树与二叉树的基本概念,包括节点、度、层次、深度等,清晰区分二叉…...
Go-zero框架修改模版进行handler统一响应封装
使用go-zero快速生成接口的时候,发现还是有一些情况不太好处理,比如说,想要自定义响应封装等等。 最开始第一版写api文件的时候,写法是这样的。 type LoginRequest {UserName string json:"userName"Password string …...
AI专题(一)----NLP2SQL探索以及解决方案
前面写了很多编码、算法、底层计算机原理等相关的技术专题,由于工作方向调整的缘故,今天开始切入AI人工智能相关介绍。本来按照规划,应该先从大模型的原理开始介绍会比较合适,但是计划赶不上变化,前面通用大模型的工作…...
深入理解 React Hooks:简化状态管理与副作用处理
在现代前端开发中,React 已经成为了最受欢迎的 JavaScript 库之一。随着 React 16.8 的发布,React Hooks 的引入彻底改变了开发者编写组件的方式。Hooks 提供了一种更简洁、更直观的方式来管理组件的状态和副作用,使得函数组件能够拥有类组件…...
Spring Boot 实现防盗链
在 Spring Boot 项目中实现防盗链可以通过多种方式,下面为你介绍两种常见的实现方法,分别是基于请求头 Referer 和基于令牌(Token)的防盗链。 基于请求头 Referer 的防盗链 这种方法通过检查请求头中的 Referer 字段,…...
Java 动态代理实现
Java 动态代理实现 一、JDK动态代理二、CGLIB动态代理三、动态代理的应用场景四、JDK代理与CGLIB代理比较 动态代理是Java中一种强大的技术,它允许在运行时创建代理对象,用于拦截对目标对象的方法调用。 一、JDK动态代理 JDK动态代理是Java标准库提供的代…...
2025年4月通信科技领域周报(4.07-4.13):6G技术加速落地 卫星通信网络迎来组网高潮
2025年4月通信科技领域周报(4.07-4.13):6G技术加速落地 卫星通信网络迎来组网高潮 目录 2025年4月通信科技领域周报(4.07-4.13):6G技术加速落地 卫星通信网络迎来组网高潮一、本周热点回顾1. 华为发布全球首…...
《手环表带保养全攻略:材质、清洁与化学品避坑指南》
系列文章目录 文章目录 系列文章目录前言一、表带材质特性与专属养护方案二、清洁剂使用红黑榜三、家庭清洁实验:化学反应警示录四、保养实践方法论总结 前言 手环作为现代生活的智能伴侣,表带材质选择丰富多样。从柔软亲肤的皮质到耐用耐磨的金属&…...
人脸扫描黑科技:多相机人脸扫描设备,打造你的专属数字分身
随着科技的迅猛发展,人脸扫描这个词已经并不陌生,通过人脸扫描设备制作超写实人脸可以为影视制作打造逼真角色、提升游戏沉浸感,还能助力教育机构等领域生产数字人以丰富教学资源,还在安防、身份识别等领域发挥关键作用࿰…...
基于Python的中国象棋小游戏的设计与实现
基于Python的中国象棋小游戏的设计与实现 第一章 绪论1.1 研究背景1.2 研究意义 第二章 需求分析2.1 需求分析2.1.1核心功能需求2.1.2 用户体验需求2.1.3 衍生功能需求 2.2 可行性分析2.2.1 技术可行性2.2.2 经济可行性2.2.3 市场可行性2.2.4 法律与合规性 第三章 概要设计3.1 …...
简单好用的在线工具
用AI写了一些在线工具,简介好用,推荐给大家,欢迎大家使用并提议意见。 网址:https://www.bittygarden.com/ 目前已有以下功能: MD5SM3SHAUnicode 编码Unicode 解码Base32 编码Base32 解码Base64 编码Base64 解码URL …...
JAVA设计模式——(1)适配器模式
JAVA设计模式——(1)适配器模式 目的理解实现优势 目的 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。 理解 可以想象成一个国标的插头,结果插座是德标的&…...
外卖市场规模巨大,是宽广赛道?京东CEO发言
大家好,我是小悟。 在竞争激烈的外卖市场中,京东作为新入局者,正以独特的战略视角和坚定的决心,重新定义外卖行业的竞争格局。 近日,京东集团CEO许冉在接受采访时表示:“外卖行业本就是一个宽广的赛道&am…...
Flutter PIP 插件 ---- iOS Video Call 自定义PIP WINDOW渲染内容
简介 画中画(Picture in Picture, PiP)是一项允许用户在使用其他应用时继续观看视频内容的功能。本文将详细介绍如何在 iOS 应用中实现 PiP 功能,包括自定义内容渲染和控制系统控件的显示。 效果展示 功能特性 已完成功能 ✅ 基础 PiP 接口实现(设置…...
TensorFlow 实现 Mixture Density Network (MDN) 的完整说明
本文档详细解释了一段使用 TensorFlow 构建和训练混合密度网络(Mixture Density Network, MDN)的代码,涵盖数据生成、模型构建、自定义损失函数与预测可视化等各个环节。 1. 导入库与设置超参数 import numpy as np import tensorflow as t…...
xml+html 概述
1.什么是xml xml 是可扩展标记语言的缩写: Extensible Markup Language。 <root><h1> text 1</h1> </root> web 应用开发,需要配置 web.xml,就是个典型的 xml文件 <web-app><servlet><servlet-name&…...
混合精度训练中的算力浪费分析:FP16/FP8/BF16的隐藏成本
在大模型训练场景中,混合精度训练已成为降低显存占用的标准方案。然而,通过NVIDIA Nsight Compute深度剖析发现,精度转换的隐藏成本可能使理论算力利用率下降40%以上。本文基于真实硬件测试数据,揭示不同精度格式的计算陷阱。…...
Python语法系列博客 · 第5期[特殊字符] 模块与包的导入:构建更大的程序结构
上一期小练习解答(第4期回顾) ✅ 练习1:判断偶数函数 def is_even(num):return num % 2 0print(is_even(4)) # True print(is_even(5)) # False✅ 练习2:求平均值 def avg(*scores):return sum(scores) / len(scores)print(…...
