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.mzph.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技术体系结构参考来源 在研究中对特征的编码和…