代码片段
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,事件也就不会从它开始冒泡。