LVGL源码(9):学会控件的使用(自定义弹窗)

news2025/5/10 9:02:55

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_DATA
    void * user_data;
#endif
    lv_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);
#endif

User_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)
{
    // 在关闭前执行恢复操作,比如恢复原先的group
    if(((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);  // 删除临时的 group
    lv_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.coloradmin.cn/o/2338722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8、表单控制:预言水晶球——React 19 复杂表单处理

一、水晶球的预言本质 "每个表单都是时空裂缝中的预言容器,"占卜课教授特里劳妮凝视着水晶球,"React-Hook-Form与Formik的融合,让数据捕获如同捕捉未来碎片!" ——以魔法部神秘事务司的预言厅为隐喻&#xf…

8 编程笔记全攻略:Markdown 语法精讲、Typora 编辑器全指南(含安装激活、基础配置、快捷键详解、使用技巧)

1 妙笔在手,编程无忧! 1.1 编程为啥要做笔记?这答案绝了! 嘿,各位键盘魔法师!学编程不记笔记,就像吃火锅不配冰可乐 —— 爽到一半直接噎住!你以为自己脑子是顶配 SSD,结…

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 中数据包含中文,…

数据结构实验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 提供了一种更简洁、更直观的方式来管理组件的状态和副作用,使得函数组件能够拥有类组件…

Java 动态代理实现

Java 动态代理实现 一、JDK动态代理二、CGLIB动态代理三、动态代理的应用场景四、JDK代理与CGLIB代理比较 动态代理是Java中一种强大的技术,它允许在运行时创建代理对象,用于拦截对目标对象的方法调用。 一、JDK动态代理 JDK动态代理是Java标准库提供的代…

人脸扫描黑科技:多相机人脸扫描设备,打造你的专属数字分身

随着科技的迅猛发展,人脸扫描这个词已经并不陌生,通过人脸扫描设备制作超写实人脸可以为影视制作打造逼真角色、提升游戏沉浸感,还能助力教育机构等领域生产数字人以丰富教学资源,还在安防、身份识别等领域发挥关键作用&#xff0…

基于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 …

外卖市场规模巨大,是宽广赛道?京东CEO发言

大家好,我是小悟。 在竞争激烈的外卖市场中,京东作为新入局者,正以独特的战略视角和坚定的决心,重新定义外卖行业的竞争格局。 近日,京东集团CEO许冉在接受采访时表示:“外卖行业本就是一个宽广的赛道&am…

Flutter PIP 插件 ---- iOS Video Call 自定义PIP WINDOW渲染内容

简介 画中画(Picture in Picture, PiP)是一项允许用户在使用其他应用时继续观看视频内容的功能。本文将详细介绍如何在 iOS 应用中实现 PiP 功能,包括自定义内容渲染和控制系统控件的显示。 效果展示 功能特性 已完成功能 ✅ 基础 PiP 接口实现(设置…

xml+html 概述

1.什么是xml xml 是可扩展标记语言的缩写&#xff1a; Extensible Markup Language。 <root><h1> text 1</h1> </root> web 应用开发&#xff0c;需要配置 web.xml&#xff0c;就是个典型的 xml文件 <web-app><servlet><servlet-name&…

Java从入门到“放弃”(精通)之旅——数组的定义与使用⑥

Java从入门到“放弃”&#xff08;精通&#xff09;之旅&#x1f680;——数组⑥ 前言——什么是数组&#xff1f; 数组&#xff1a;可以看成是相同类型元素的一个集合&#xff0c;在内存中是一段连续的空间。比如现实中的车库&#xff0c;在java中&#xff0c;包含6个整形类…

如何对docker镜像存在的gosu安全漏洞进行修复——筑梦之路

这里以mysql的官方镜像为例进行说明&#xff0c;主要流程为&#xff1a; 1. 分析镜像存在的安全漏洞具体是什么 2. 根据分析结果有针对性地进行修复处理 3. 基于当前镜像进行修复安全漏洞并复核验证 # 镜像地址mysql:8.0.42 安全漏洞现状分析 dockerhub网站上获取该镜像的…

基于springboot的老年医疗保健系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…