LVGL 事件系统
事件是 LVGL 响应用户操作(如点击、滑动)或控件状态变化的核心机制,通过 “事件绑定 - 回调函数” 实现交互逻辑。
事件核心特点
- 多绑定支持:一个回调函数可绑定多个对象(如一个 “计数回调” 绑定两个按钮);一个对象可绑定多个回调函数(如按钮同时绑定 “单击回调” 和 “长按回调”),回调按绑定顺序执行。
- 事件冒泡:给对象添加
lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE)后,子对象的事件会传递给父对象;若父对象也启用冒泡,事件会继续向上传递(直到顶层屏幕),适用于批量处理子对象事件。 - 状态化触发:回调函数可通过
lv_event_get_code(e)区分事件类型(如单击、长按),仅处理目标事件。 - 用户数据传递:绑定事件时可携带自定义数据(如标签指针),避免使用全局变量,提升代码模块化。
事件类型分类
事件分为 5 大类,核心事件及触发场景如下(完整定义见lvgl/src/core/lv_event.h):
| 事件类别 | 核心事件宏 | 触发场景(文件原文 + 补充说明) |
|---|---|---|
| 输入设备事件 | LV_EVENT_PRESSED |
对象被首次按下(仅触发一次,区别于LV_EVENT_PRESSING的持续触发) |
LV_EVENT_PRESSING |
对象被持续按下(按下期间不断触发,适用于实时响应的场景,如拖动) | |
LV_EVENT_SHORT_CLICKED |
对象被短按(按下后快速释放,未超过 “长按时间阈值”) | |
LV_EVENT_LONG_PRESSED |
对象被长按(按下时间超过lv_conf.h中LV_LONG_PRESS_TIME(默认 1000ms),仅触发一次) |
|
LV_EVENT_LONG_PRESSED_REPEAT |
长按后重复触发(长按时间超过阈值后,每隔LV_LONG_PRESS_REPEAT_TIME(默认 100ms)触发一次) |
|
LV_EVENT_CLICKED |
对象被单击(按下后释放,且未发生滑动,是最常用的交互事件) | |
LV_EVENT_RELEASED |
对象被释放(无论是否滑动,只要按下后松开就触发,用于清理按下状态) | |
LV_EVENT_SCROLL |
对象发生滑动(如列表滚动、滑动条拖动,滚动过程中持续触发) | |
LV_EVENT_FOCUSED |
对象获得焦点(如输入框被点击、键盘 Tab 键切换到该对象) | |
LV_EVENT_DEFOCUSED |
对象失去焦点(如点击其他区域、切换到其他控件) | |
| 绘图事件 | LV_EVENT_COVER_CHECK |
检查对象是否完全覆盖某个区域(用于优化绘图性能,避免无效刷新) |
LV_EVENT_DRAW_MAIN_BEGIN |
控件开始绘制主体区域(用于自定义绘图,如在按钮上额外绘制图标) | |
| 子对象管理事件 | LV_EVENT_CHILD_CREATED |
父对象新增子对象时触发(如给按钮添加标签时,按钮会收到该事件) |
LV_EVENT_CHILD_DELETED |
父对象的子对象被删除时触发(如删除按钮上的标签时,按钮会收到该事件) | |
| 自定义事件 | LV_EVENT_LAST+0,LV_EVENT_LAST+1 |
用户自定义事件(如 “数据加载完成”“网络断开”,需手动调用lv_event_send()触发) |
常用函数解析:
//头文件lv_obj_event.h
/*** 为对象添加事件回调函数(事件响应的核心入口,必用函数)* @param obj 目标对象指针(lv_obj_t*,不可为NULL),需绑定事件回调的对象(如按钮、滑块、标签等)* @param event_cb 事件回调函数(lv_event_cb_t),函数原型为void (*lv_event_cb_t)(lv_event_t* e),事件触发时自动执行;回调内可通过lv_event_xxx系列函数获取事件信息* @param filter 事件筛选器(lv_event_code_t),指定仅响应的事件类型;例:LV_EVENT_CLICKED(点击)、LV_EVENT_VALUE_CHANGED(值变化)、LV_EVENT_PRESSED(按下),填LV_EVENT_ALL可响应所有事件* @param user_data 自定义数据(void*),将随事件传递给回调函数;可通过lv_event_get_user_data(e)在回调中获取;常用于传递对象关联的上下文(如参数配置、关联控件指针)* @return 事件描述符指针(lv_event_dsc_t*),用于后续移除该回调(如lv_obj_remove_event_dsc);内存不足时返回NULL,需检查返回值避免空指针操作* @note 同一对象可添加多个回调,触发时按添加顺序执行;若需移除特定回调,需保存此返回的event_dsc;* user_data若为栈内存(如局部变量),需确保对象未销毁前内存不释放(建议用全局变量、静态变量或堆内存(lv_malloc分配));* 回调函数中避免长时间阻塞操作(如死循环、延时),会导致界面卡顿甚至无响应*/
lv_event_dsc_t * lv_obj_add_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter, void * user_data);/*** 向对象手动发送指定事件(主动触发事件,常用调试或联动场景)* @param obj 目标对象指针(lv_obj_t*,不可为NULL),需接收事件的对象* @param event_code 事件类型(lv_event_code_t),需传入LVGL预定义事件值;例:手动触发按钮点击事件LV_EVENT_CLICKED、触发控件值更新LV_EVENT_VALUE_CHANGED* @param param 自定义参数(void*),将传递给对象的事件回调;无需传参时填NULL* @return 执行结果(lv_result_t):LV_RESULT_OK表示对象未被事件删除,LV_RESULT_INVALID表示对象在回调中被删除(后续不可操作该obj)* @note 手动发送事件会完整触发对象所有匹配该事件的回调,与用户操作触发的事件逻辑一致;* 若回调中调用lv_obj_del(obj)删除当前对象,此函数返回LV_RESULT_INVALID,需立即停止对该obj的后续访问(如修改样式、获取属性);* 不建议频繁发送LV_EVENT_DRAW系列渲染相关事件,会增加GPU/CPU负载,影响界面帧率*/
lv_result_t lv_obj_send_event(lv_obj_t * obj, lv_event_code_t event_code, void * param);/*** 获取事件的原始触发对象(区分事件源头,常用冒泡场景)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数的参数中传入* @return 原始目标对象指针(lv_obj_t*),即最初触发事件的对象;例:点击子按钮时,即使事件冒泡到父容器,此函数仍返回子按钮* @note 与lv_event_get_current_target_obj()的核心区别:前者始终指向“事件源头”,后者指向“当前执行回调的对象”(如父容器回调中返回父容器);* 常用于父容器回调中区分“哪个子控件触发了事件”(如多个按钮共用父容器回调时,通过此函数判断点击的按钮);* 仅在事件回调内部调用有效,外部调用返回NULL*/
lv_obj_t * lv_event_get_target_obj(lv_event_t * e);/*** 移除对象中指定的事件回调(释放资源,避免内存泄漏)* @param obj 目标对象指针(lv_obj_t*,不可为NULL),需移除回调的对象* @param dsc 事件描述符指针(lv_event_dsc_t*,不可为NULL),即lv_obj_add_event_cb的返回值* @return 移除结果(bool):true表示移除成功,false表示描述符无效(如已移除、不属于该对象)* @note 移除后,该event_dsc指针失效,不可再使用;* 若对象被删除(lv_obj_del(obj)),LVGL会自动清理其所有事件回调,无需手动调用此函数;* 建议在对象关联的上下文销毁时(如页面关闭),手动移除回调,避免悬空指针风险*/
bool lv_obj_remove_event_dsc(lv_obj_t * obj, lv_event_dsc_t * dsc);/*** 获取触发事件的输入设备(判断输入类型,常用交互逻辑)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数参数传入* @return 输入设备指针(lv_indev_t*),例:触摸设备(LV_INDEV_TYPE_POINTER)、键盘(LV_INDEV_TYPE_KEYPAD)、旋转编码器(LV_INDEV_TYPE_ENCODER);非输入事件(如LV_EVENT_SIZE_CHANGED)返回NULL* @note 需配合lv_indev_get_type(indev)判断设备类型,例:区分事件是触摸触发还是键盘触发,从而执行不同逻辑;* 仅在输入相关事件中有效(如LV_EVENT_CLICKED、LV_EVENT_KEY、LV_EVENT_ROTARY),其他事件返回NULL*/
lv_indev_t * lv_event_get_indev(lv_event_t * e);/*** 按回调函数地址移除事件(批量清理相同回调,常用统一管理场景)* @param obj 目标对象指针(lv_obj_t*,不可为NULL)* @param event_cb 需移除的回调函数地址(lv_event_cb_t,不可为NULL),所有绑定该函数的回调都会被移除* @return 移除结果(bool):true表示至少移除1个回调,false表示未找到匹配的回调* @note 此函数会移除对象中所有绑定该event_cb的回调(无论filter和user_data是否相同);* 若仅需移除某个特定回调(而非所有相同函数的回调),建议用lv_obj_remove_event_dsc(需保存event_dsc);* 移除后,对应的event_dsc失效,不可再使用*/
bool lv_obj_remove_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb);
//头文件lv_event.h
/*** 向事件列表添加事件回调(事件系统核心注册接口)* @param list 事件列表指针(lv_event_list_t*,不可为NULL),需绑定回调的事件列表(通常从对象中获取,如obj->event_list)* @param cb 事件回调函数(lv_event_cb_t),函数原型为void (*lv_event_cb_t)(lv_event_t* e),事件触发时执行* @param filter 事件筛选器(lv_event_code_t),指定仅响应的事件类型;填LV_EVENT_ALL响应所有事件,填具体事件(如LV_EVENT_CLICKED)仅响应该事件* @param user_data 自定义数据(void*),将随事件传递给回调;可通过lv_event_get_user_data(e)在回调中获取;用于传递上下文信息(如关联控件、配置参数)* @return 事件描述符指针(lv_event_dsc_t*),用于后续移除该回调;内存不足时返回NULL,需检查返回值避免空指针操作* @note 同一事件列表可添加多个回调,触发时按添加顺序执行;若需精准移除单个回调,需保存此返回的event_dsc;* user_data若为栈内存(如局部变量),需确保事件列表生命周期内内存不释放(建议用全局变量、静态变量或lv_malloc分配的堆内存);* 回调函数中避免执行耗时操作(如文件读写、延时),会阻塞事件处理流程,导致界面卡顿*/
lv_event_dsc_t * lv_event_add(lv_event_list_t * list, lv_event_cb_t cb, lv_event_code_t filter, void * user_data);/*** 向事件列表发送指定事件(主动触发事件,常用调试或联动场景)* @param list 事件列表指针(lv_event_list_t*,不可为NULL),需接收事件的目标事件列表* @param e 事件描述符指针(lv_event_t*,不可为NULL),需提前初始化(包含事件类型、目标对象、参数等信息)* @param preprocess 是否预处理(bool):true表示先执行标记LV_EVENT_PREPROCESS的回调,false按正常顺序执行* @return 执行结果(lv_result_t):LV_RESULT_OK表示事件处理完成,LV_RESULT_INVALID表示事件处理被中断(如调用lv_event_stop_processing)* @note 发送事件前需确保e的关键字段(如event_code、target、current_target)已正确初始化;* 若回调中调用lv_event_stop_processing(e),此函数会提前返回,未执行的回调将被跳过;* 不建议频繁发送LV_EVENT_DRAW系列渲染事件,会增加CPU/GPU负载,影响界面帧率*/
lv_result_t lv_event_send(lv_event_list_t * list, lv_event_t * e, bool preprocess);/*** 获取事件的原始目标(区分事件源头,常用冒泡场景)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数参数中传入* @return 原始目标指针(void*),即最初触发事件的对象/数据;例:子控件触发事件后冒泡到父容器,此函数仍返回子控件地址* @note 与lv_event_get_current_target(e)的核心区别:前者始终指向“事件源头”,后者指向“当前处理回调的目标”(如父容器回调中返回父容器);* 需强制转换为对应类型使用(如lv_obj_t* obj = (lv_obj_t*)lv_event_get_target(e));* 仅在事件回调内部调用有效,外部调用返回NULL*/
void * lv_event_get_target(lv_event_t * e);/*** 获取事件的当前目标(定位当前处理对象,常用多层级回调场景)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数参数中传入* @return 当前目标指针(void*),即当前正在执行回调的对象/数据;例:父容器处理子控件冒泡事件时,返回父容器地址* @note 常用于回调中判断“当前处理的对象是否为预期目标”(如避免父容器误处理非关联子控件的事件);* 需强制转换为对应类型使用(如lv_obj_t* curr_obj = (lv_obj_t*)lv_event_get_current_target(e));* 若事件未冒泡,当前目标与原始目标一致*/
void * lv_event_get_current_target(lv_event_t * e);/*** 获取事件类型(判断事件种类,回调核心逻辑分支依据)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数参数中传入* @return 事件类型(lv_event_code_t),即触发回调的事件种类,如LV_EVENT_CLICKED(点击)、LV_EVENT_VALUE_CHANGED(值变化)、LV_EVENT_DELETE(删除)* @note 是回调函数中分支逻辑的核心依据(例:if(lv_event_get_code(e) == LV_EVENT_CLICKED) { ... });* 若为自定义事件,返回值为通过lv_event_register_id()注册的自定义ID;* 不可直接将返回值与自定义整数比较,需用注册时保存的自定义事件ID*/
lv_event_code_t lv_event_get_code(lv_event_t * e);/*** 获取事件的自定义数据(回调中获取上下文,常用参数传递)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数参数中传入* @return 自定义数据指针(void*),即调用lv_event_add()时传入的user_data;未传数据时返回NULL* @note 是回调函数获取外部上下文的唯一方式(例:通过此函数获取关联的配置结构体、子控件指针);* 需强制转换为对应类型使用(如my_config_t* cfg = (my_config_t*)lv_event_get_user_data(e));* 若user_data指向堆内存,需确保回调中不提前释放,避免悬空指针*/
void * lv_event_get_user_data(lv_event_t * e);/*** 停止事件冒泡(阻止事件向上传递,常用边界控制)* @param e 事件描述符指针(lv_event_t*,不可为NULL),仅能从事件回调函数参数中传入* @note 仅对支持冒泡的事件有效(如LV_EVENT_CLICKED、LV_EVENT_VALUE_CHANGED);调用后,事件不再传递给父对象的事件列表;* 需在回调早期调用(如回调函数开头),若已执行部分逻辑后调用,仅影响后续冒泡,不回滚已执行操作;* 与lv_event_stop_processing(e)的区别:前者阻止事件向上冒泡,后者停止当前事件列表的后续回调执行*/
void lv_event_stop_bubbling(lv_event_t * e);
事件处理完整流程
事件处理的核心是:用户操作 / 系统触发 → 事件生成 → 传递到对象 → 执行绑定的回调函数。具体分 5 步,结合代码示例说明:
步骤 1:创建 UI 对象(事件的载体)
事件必须依附于具体的 UI 对象(如按钮、滑块等),先创建一个对象作为事件的 “接收者”。
// 创建一个按钮(作为事件载体)
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 在当前屏幕创建按钮
lv_obj_set_pos(btn, 100, 100); // 位置:x=100, y=100
lv_obj_set_size(btn, 120, 50); // 大小:宽120,高50
步骤 2:编写事件回调函数(事件的处理逻辑)
回调函数是事件触发后执行的代码,通过 LVGL 提供的lv_event_get_xxx函数获取事件详情(如事件类型、触发对象等)。
// 回调函数:处理按钮的点击、释放等事件
static void btn_event_cb(lv_event_t * e) {// 获取事件类型(判断是“点击”“释放”还是其他事件)lv_event_code_t code = lv_event_get_code(e);// 获取触发事件的原始对象(这里是按钮)lv_obj_t * btn = lv_event_get_target(e);// 根据事件类型执行不同操作if(code == LV_EVENT_CLICKED) {lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF0000), LV_PART_MAIN); // 点击后变红}else if(code == LV_EVENT_RELEASED) {lv_obj_set_style_bg_color(btn, lv_color_hex(0x00FF00), LV_PART_MAIN); // 释放后变绿}
}
步骤 3:绑定回调函数到对象(建立事件与处理逻辑的关联)
通过lv_obj_add_event_cb函数,将回调函数绑定到对象上,并指定需要响应的事件类型(如 “点击”“释放”)。
// 给按钮绑定回调函数,指定响应“点击”和“释放”事件
lv_obj_add_event_cb(btn, // 目标对象(按钮)btn_event_cb, // 回调函数(上面定义的btn_event_cb)LV_EVENT_CLICKED | LV_EVENT_RELEASED, // 响应的事件类型(点击+释放)NULL // 自定义用户数据(这里无需传递,填NULL)
);
- 绑定后,当按钮被点击或释放时,
btn_event_cb会自动被调用。
步骤 4:事件的触发与传递(事件如何被触发并传递)
当用户操作对象(如点击按钮)或系统触发事件(如对象被删除)时,LVGL 会自动生成事件,并按规则传递:
-
事件触发:例如用户用鼠标点击按钮,LVGL 的输入设备系统会检测到这个操作,生成
LV_EVENT_CLICKED事件。 -
事件传递(冒泡机制):事件会先传递给触发对象(按钮),执行其绑定的回调;若未被阻止,事件会向上 “冒泡” 到父对象(如按钮的父容器),执行父对象的回调。
// 示例:给按钮的父容器也绑定回调,演示冒泡 lv_obj_t * parent = lv_obj_create(lv_scr_act()); // 创建父容器 lv_obj_set_size(parent, 300, 200); btn = lv_btn_create(parent); // 按钮的父对象是parent(此时按钮在容器内)// 父容器的回调(接收子按钮的冒泡事件) static void parent_event_cb(lv_event_t * e) {if(lv_event_get_code(e) == LV_EVENT_CLICKED) {LV_LOG_USER("父容器收到了按钮的点击事件(冒泡)");} } lv_obj_add_event_cb(parent, parent_event_cb, LV_EVENT_CLICKED, NULL);- 点击按钮时,会先执行
btn_event_cb,再执行parent_event_cb(冒泡效果)。
- 点击按钮时,会先执行
步骤 5:回调中控制事件(可选:阻止冒泡或终止处理)
在回调函数中,可通过lv_event_stop_bubbling阻止事件继续向上传递,或用lv_event_stop_processing终止当前事件的所有后续处理。
static void btn_event_cb(lv_event_t * e) {if(lv_event_get_code(e) == LV_EVENT_CLICKED) {// 阻止事件冒泡到父容器lv_event_stop_bubbling(e); // 此时父容器的回调不会被执行}
}
总结:一句话流程
创建对象 → 写回调(处理逻辑) → 绑定回调到对象(指定事件) → 用户操作触发事件 → 事件传递(可冒泡) → 回调执行(可控制传递)。
自定义事件示例:
// 自定义事件ID(全局变量,运行时注册)
uint32_t MY_EVENT_CUSTOM;// 事件回调函数(用if-else判断事件类型,避免switch-case限制)
static void btn_event_cb(lv_event_t *e) {// 获取事件类型和目标按钮uint32_t event_code = lv_event_get_code(e);lv_obj_t *btn = lv_event_get_target(e);// 处理不同事件if (event_code == LV_EVENT_CLICKED) {// 点击按钮时触发:按钮变红色lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF0000), LV_PART_MAIN);LV_LOG_USER("The button was clicked (system event)");} else if (event_code == MY_EVENT_CUSTOM) {// 自定义事件触发:按钮变绿色lv_obj_set_style_bg_color(btn, lv_color_hex(0x00FF00), LV_PART_MAIN);LV_LOG_USER("Received a custom event! parameters:%s", (char*)lv_event_get_param(e));}
}
// 创建对象→注册事件→发送事件
void test(void)
{// 创建一个按钮作为事件目标lv_obj_t *btn = lv_btn_create(lv_scr_act());lv_obj_set_size(btn, 200, 100); // 大小200x100lv_obj_center(btn); // 屏幕居中lv_obj_set_style_bg_color(btn, lv_color_hex(0x0000FF), LV_PART_MAIN); // 初始蓝色// 注册事件回调(同时监听系统点击事件和自定义事件)lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL); // 系统事件lv_obj_add_event_cb(btn, btn_event_cb, MY_EVENT_CUSTOM, NULL); // 自定义事件// 主动发送自定义事件(带参数"测试数据")lv_obj_send_event(btn, MY_EVENT_CUSTOM, "hello custom event");
}