解码LVGL事件

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.hLV_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);  // 大小200x100
    lv_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");
}
posted @ 2025-11-03 21:21  YouEmbedded  阅读(142)  评论(0)    收藏  举报