LVGL 事件冒泡机制

代码片段

cpp 复制代码
/* 事件冒泡 */
#if 1
static void my_event_cb(lv_event_t * e)
{
    lv_obj_t * obj = lv_event_get_target(e);            // 获取事件"最初"触发的对象(例如点了 obj4,这里就是 obj4)
    lv_obj_t * parent = lv_event_get_current_target(e); // 获取"当前正在执行回调"的对象(回调挂在哪个对象上/事件冒泡到哪个对象上,这里就指向哪个对象)
    lv_event_code_t code = lv_event_get_code(e);        // 获取当前部件触发的事件代码
    lv_obj_t * label = lv_event_get_user_data(e);       // 获取添加事件时传递的用户数据

    switch(code){
        case LV_EVENT_PRESSED:
            lv_label_set_text(label, "LV_EVENT_PRESSED");
            lv_obj_set_style_bg_color(parent, lv_color_hex(0xc43e1c), 0);
            lv_obj_set_style_bg_color(obj, lv_color_hex(0xc43e1c), 0);
            LV_LOG_USER("LV_EVENT_PRESSED\n");
            break;
        case LV_EVENT_CLICKED:
            lv_label_set_text(label, "LV_EVENT_CLICKED");
            lv_obj_remove_local_style_prop(parent, LV_STYLE_BG_COLOR, 0); 
            lv_obj_remove_local_style_prop(obj, LV_STYLE_BG_COLOR, 0);  
            LV_LOG_USER("LV_EVENT_CLICKED\n");
            break;
        default:
            //LV_LOG_USER("NONE\n");
            break;
    }
}

void lesson_2_8_2(void)
{
    /* 创建一个基础对象 obj1 */
    lv_obj_t * obj1 = lv_obj_create(lv_screen_active()); //函数参数为父对象,返回新对象的指针,这里父对象是屏幕:lv_screen_active()
    lv_obj_set_size(obj1, 450, 250);
    lv_obj_center(obj1);                                // 将对象与其父对象的中心对齐,这里的父对象是屏幕:lv_screen_active()

    /* 以 obj1 创建一个基础对象 obj2 */
    lv_obj_t * obj2 = lv_obj_create(obj1);
    lv_obj_set_size(obj2, 400, 200);
    lv_obj_center(obj2);                                // 将对象与其父对象的中心对齐,这里的父对象是屏幕:obj1
    lv_obj_add_flag(obj2, LV_OBJ_FLAG_EVENT_BUBBLE);    // 启用事件冒泡,将接收到的所有事件传播给父级

    /* 以 obj2 创建一个基础对象 obj3 */
    lv_obj_t * obj3 = lv_obj_create(obj2);
    lv_obj_set_size(obj3, 350, 150);
    lv_obj_center(obj3);                                // 将对象与其父对象的中心对齐,这里的父对象是屏幕:obj2
    lv_obj_add_flag(obj3, LV_OBJ_FLAG_EVENT_BUBBLE);    // 启用事件冒泡,将接收到的所有事件传播给父级

    /* 以 obj3 创建一个基础对象 obj4 */
    lv_obj_t * obj4 = lv_obj_create(obj3);
    lv_obj_set_size(obj4, 300, 100);
    lv_obj_center(obj4);                                // 将对象与其父对象的中心对齐,这里的父对象是屏幕:obj3
    lv_obj_add_flag(obj4, LV_OBJ_FLAG_EVENT_BUBBLE);    // 启用事件冒泡,将接收到的所有事件传播给父级

    /* 以屏幕为父类,创建一个label部件(对象) */
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "test");                               // 设置label展示的文字
    lv_obj_align_to(label, obj1, LV_ALIGN_OUT_TOP_MID, 0, 0);       // 将label相对于obj1对齐

    // 将给obj1添加事件回调函数,所有的事件类型都能触发该回调函数
    lv_obj_add_event_cb(obj1, my_event_cb, LV_EVENT_ALL, label);
}
#endif // 1

问题1

选中的函数对象为啥是obj1,而不是obj4,最顶层的对象呢,点击任意对象都能触发my_event_cb回调函数吗

回答

这里的关键是 事件"冒泡"+ 回调挂载在哪个对象上。

你把回调用 lv_obj_add_event_cb(obj1, my_event_cb, LV_EVENT_ALL, label); 挂在了 obj1 上,所以 my_event_cb 只会在 obj1 接收到事件时被调用(包括:事件本来就发生在 obj1 上;或子对象把事件冒泡上传到 obj1)。

obj2/obj3/obj4 都加了 LV_OBJ_FLAG_EVENT_BUBBLE,含义是:该对象收到的事件,会继续转发给父对象。因此点击 obj4 时,事件链大致是:obj4 -> obj3 -> obj2 -> obj1,最终 obj1 也会收到这个事件,于是触发你挂在 obj1 上的 my_event_cb。

为什么你在回调里"触发对象"看起来是 obj1?

在 my_event_cb 里你用了两个 API:

lv_event_get_target(e):返回最初触发事件的对象(比如你点的是 obj4,这里就是 obj4)。

lv_event_get_current_target(e):返回当前正在执行该回调的对象(回调挂在哪个对象上、当前就会是哪个对象)。因为你只在 obj1 上注册了回调,所以这里通常就是 obj1。

你代码里把 lv_event_get_current_target(e) 存到 parent 变量,其实更准确叫 "current_target/当前目标",它在这里是 obj1,不一定是 lv_event_get_target(e) 的"直接父对象"。

"点击任意对象都能触发 my_event_cb 吗?"

点击 obj1/obj2/obj3/obj4 都会触发(前提是点到的对象确实收到了输入事件;而 obj2/obj3/obj4 都开启了冒泡,所以会一路传到 obj1)。

但不是"屏幕上任意东西"都行:例如你点到 label(它不是 obj1 的子对象,而是直接挂在屏幕上),事件不会冒泡到 obj1,因此不会触发挂在 obj1 的这个回调。

问题2

obj2/obj3/obj4对象没有调用lv_obj_add_event函数,为啥也可以收到事件呢

回答

在 LVGL 里,"对象会不会收到事件"和"你有没有给它注册回调"是两回事。

lv_obj_add_event_cb(...) 做的事是:给这个对象的事件列表里挂一个"用户回调",用于监听/处理事件。但是 输入事件(点击/按下/滚动等)是 LVGL 的输入设备系统自动分发的:当你用鼠标/触摸点到屏幕时,LVGL 会做命中测试(hit-test),找到被点中的那个最深层对象(比如 obj4),然后把事件发送给它 ------即使你没有给 obj4 注册任何回调,它依然"收到"了这个事件("收到后没有用户回调要执行",可能只跑默认处理)。

因此你看到的现象是:

点 obj4 时:LVGL 先把 LV_EVENT_PRESSED/CLICKED/... 发给 obj4(target=obj4)。

因为 obj4/obj3/obj2 开了 LV_OBJ_FLAG_EVENT_BUBBLE,事件会继续向父级上传:obj4 -> obj3 -> obj2 -> obj1。
你只在 obj1 上注册了 my_event_cb,所以最终在 obj1 这一层,才会调用到你的 my_event_cb(这时 current_target=obj1,但 target 仍然是最初的 obj4)。

再补一句你可能关心的点:

之所以 obj2/obj3/obj4 能成为点击目标,通常是因为基础对象默认就是可点击/可命中(例如带 LV_OBJ_FLAG_CLICKABLE 等默认行为)。如果某个对象被设置为不可点击、被遮挡、或命中测试不通过,那它就不会成为 target,事件也就不会从它开始冒泡。

相关推荐
lanhuazui105 小时前
LVGL定时器中几个问题
用户界面
工控小龙人4 天前
环保设备HMI:废气处理的浓度监控界面
ui·人机交互·用户界面
工控小龙人5 天前
医疗器械HMI:输液泵的精准控制界面
ui·人机交互·用户界面
工控小龙人6 天前
船舶维修HMI:船舶发动机的检修诊断界面
ui·人机交互·用户界面
rhett. li8 天前
FreeBSD系统中使用clang/clang++编译Skia源码的方法
c++·ui·用户界面
rhett. li8 天前
Windows系统中使用MinGW-W64(gcc/g++或LLVM)编译Skia源码的方法
c++·windows·ui·用户界面
初级代码游戏24 天前
win11任务栏很美,但不好用
用户界面
许泽宇的技术分享2 个月前
A2UI与AG-UI深度对比:两大AI界面协议的异同与选择
用户界面·智能体·ag-ui·a2ui
许泽宇的技术分享2 个月前
当AI学会“画“界面:A2UI如何让智能体拥有UI表达能力
人工智能·生成式ai·前端开发·用户界面·a2ui