2. 事件类型详解:LV_EVENT_CLICKED、LV_EVENT_PRESSING、LV_EVENT_LONG_PRESSED、LV_EVENT_VALUE_CHANGED 等核心事件

事件类型,说白了就是告诉系统「用户到底干了什么」。LVGL 里定义了二十多种事件,但日常开发中,80% 的场景只用其中五六个。我刚开始学 LVGL 时,总想把所有事件都记住,后来发现——记住核心的几个,剩下的查手册就行。

这一节,咱们就把最常用的几个事件掰开揉碎讲清楚。你想想看,一个按钮按下去,系统到底经历了什么?

核心观点:事件不是孤立的,它们有触发顺序、有上下文、有生命周期。理解了这个顺序,你才能写出不出 bug 的交互逻辑。

2.1 事件的生命周期:一次点击的完整旅程

先看一张图。这是我个人习惯用的「事件触发时序图」,能帮你快速建立整体认知。

一次点击的完整事件触发时序 LV_EVENT_PRESSED 手指按下瞬间 LV_EVENT_PRESSING 持续按压中 LV_EVENT_LONG_PRESSED 长按触发(默认400ms) LV_EVENT_RELEASED 手指抬起 LV_EVENT_CLICKED 短按释放后触发 LV_EVENT_LONG_PRESSED_REPEAT 长按后周期性触发 💡 注意:CLICKED 和 LONG_PRESSED 是互斥的——要么短按触发 CLICKED,要么长按触发 LONG_PRESSED,不会同时发生。

嗯,这里要注意:CLICKED 和 LONG_PRESSED 是互斥的。系统会先等待一段时间(默认 400ms),如果手指在 400ms 内抬起,触发 CLICKED;如果超过 400ms 还没抬起,触发 LONG_PRESSED,CLICKED 就不再触发了。

我的经验:曾经有个项目,用户反馈按钮「偶尔没反应」。排查了半天,发现是长按阈值设得太短(200ms),用户正常点击稍慢一点就被识别成长按了。后来我把阈值调到 500ms,问题解决。所以——长按阈值要根据实际使用场景调整,别用默认值硬扛。

2.2 逐个拆解:核心事件详解

2.2.1 LV_EVENT_CLICKED —— 最常用的「点击完成」事件

这是你用得最多的事件。用户按下并释放一个对象,且没有触发长按,就会收到 CLICKED。说白了,就是「一次完整的短按操作」。我个人习惯把按钮的「确认操作」都挂在这个事件上。

static void btn_event_cb(lv_event_t * e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_CLICKED) {
        LV_LOG_USER("按钮被点击了!");
        // 执行你的业务逻辑
    }
}

lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
避坑指南:我曾经把业务逻辑写在 LV_EVENT_PRESSED 里,结果用户手指刚碰到屏幕就触发了,根本来不及取消。记住:CLICKED 才是「用户确认操作完成」的信号,PRESSED 只是「用户开始操作」的信号。

2.2.2 LV_EVENT_PRESSING —— 实时反馈的利器

这个事件在用户按住屏幕的整个过程中持续触发。你想想看,什么场景需要它?

  • 长按进度条:用户按住按钮时,进度条持续增长
  • 游戏摇杆:手指按住方向键,角色持续移动
  • 音量调节:按住音量键,数值持续变化
static void slider_event_cb(lv_event_t * e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_PRESSING) {
        lv_obj_t * slider = lv_event_get_target(e);
        int32_t val = lv_slider_get_value(slider);
        // 实时更新显示
        lv_label_set_text_fmt(label, "当前值: %d", val);
    }
}

这里有个性能问题要注意:PRESSING 触发频率很高(每 20ms 左右一次)。如果你的回调里做了大量计算或重绘,界面会卡顿。我建议在 PRESSING 回调里只做轻量操作,比如更新一个数值,别做文件读写或复杂运算。

2.2.3 LV_EVENT_LONG_PRESSED —— 长按操作的入口

用户按住屏幕超过阈值(默认 400ms)后触发。常用于「快捷菜单」「删除确认」「语音唤醒」等场景。

属性 默认值 说明
长按触发时间 400 ms 可通过 lv_indev_set_long_press_time() 修改
长按重复触发 否 如需重复触发,使用 LV_EVENT_LONG_PRESSED_REPEAT
与 CLICKED 关系 互斥 触发 LONG_PRESSED 后不再触发 CLICKED
static void long_press_cb(lv_event_t * e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_LONG_PRESSED) {
        LV_LOG_USER("长按触发!显示快捷菜单");
        // 弹出菜单
    }
}

lv_obj_add_event_cb(btn, long_press_cb, LV_EVENT_LONG_PRESSED, NULL);
我的建议:如果你的应用需要「长按删除」功能,最好加一个震动反馈或视觉反馈(比如按钮颜色变深),让用户知道「已经进入长按状态了」。否则用户会困惑——「我按了这么久,到底有没有触发?」

2.2.4 LV_EVENT_VALUE_CHANGED —— 状态变化的通用通知

这个事件比较特殊。它不是由用户输入直接触发的,而是由对象的值发生变化时触发。哪些对象会触发它?

  • 滑块(Slider):拖动滑块改变数值时
  • 开关(Switch):切换开关状态时
  • 下拉列表(Dropdown):选择不同选项时
  • 复选框(Checkbox):勾选/取消勾选时
  • 滚动条(Roller):滚动选择时
static void switch_event_cb(lv_event_t * e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_VALUE_CHANGED) {
        lv_obj_t * sw = lv_event_get_target(e);
        bool is_on = lv_obj_has_state(sw, LV_STATE_CHECKED);
        if(is_on) {
            LV_LOG_USER("开关已打开");
        } else {
            LV_LOG_USER("开关已关闭");
        }
    }
}

你可能会问:「为什么不用 CLICKED 监听开关?」嗯,这里有个细节:CLICKED 只告诉你「用户点击了」,但 VALUE_CHANGED 告诉你「状态变了」。如果用户点击开关但状态没变(比如被程序锁定了),CLICKED 仍然会触发,但 VALUE_CHANGED 不会。所以——监听状态变化,用 VALUE_CHANGED 更准确。

2.3 事件组合实战:一个按钮实现三种操作

讲完单个事件,咱们来点实战。我经常在项目中用一个按钮实现「短按执行、长按菜单、双击特殊功能」。下面这个例子,一个按钮同时响应 CLICKED、LONG_PRESSED 和 PRESSING:

static void multi_event_cb(lv_event_t * 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_CLICKED:
            lv_label_set_text(label, "短按:执行任务");
            break;
        case LV_EVENT_LONG_PRESSED:
            lv_label_set_text(label, "长按:打开设置");
            break;
        case LV_EVENT_PRESSING:
            lv_label_set_text(label, "按住中...");
            break;
        default:
            break;
    }
}

lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_t * info_label = lv_label_create(lv_scr_act());

// 同一个回调,监听三个事件
lv_obj_add_event_cb(btn, multi_event_cb, LV_EVENT_CLICKED, info_label);
lv_obj_add_event_cb(btn, multi_event_cb, LV_EVENT_LONG_PRESSED, info_label);
lv_obj_add_event_cb(btn, multi_event_cb, LV_EVENT_PRESSING, info_label);
关键点:同一个回调函数可以同时监听多个事件。在回调里通过 lv_event_get_code() 区分当前触发的是哪个事件。这样代码更紧凑,也更容易维护。

2.4 事件优先级与触发顺序

最后,咱们用一张表总结一下核心事件的触发顺序和优先级。这张表我建议你收藏,调试时经常用到。

触发顺序 事件名称 触发条件 典型用途
1 LV_EVENT_PRESSED 手指按下瞬间 按下反馈(变色、震动)
2 LV_EVENT_PRESSING 按住期间持续触发 实时更新、进度反馈
3 LV_EVENT_LONG_PRESSED 按住超过阈值(默认400ms) 快捷菜单、删除确认
4 LV_EVENT_RELEASED 手指抬起瞬间 释放反馈
5 LV_EVENT_CLICKED 短按释放后(未触发长按) 确认操作、按钮点击
6 LV_EVENT_VALUE_CHANGED 对象值发生变化时 滑块、开关、下拉列表状态变化

看到这个顺序,你应该明白了:事件不是同时发生的,而是一条流水线。PRESSED 最先触发,然后 PRESSING 持续触发,最后根据用户的操作时长决定是 CLICKED 还是 LONG_PRESSED。VALUE_CHANGED 则独立于这个流水线,由对象内部状态变化驱动。

再次提醒:不要在 PRESSED 或 PRESSING 里做「确认操作」。我曾经见过有人把「提交表单」写在 PRESSED 里,结果用户手指刚碰到屏幕就提交了,想取消都来不及。确认操作一定要放在 CLICKED 里。

好了,这一节的核心事件就讲到这里。你只要记住:CLICKED 是「确认」,PRESSING 是「持续反馈」,LONG_PRESSED 是「快捷操作」,VALUE_CHANGED 是「状态变化」。把这四个用好,90% 的交互场景都能覆盖。