2. LVGL基础回顾
事件类型,说白了就是告诉系统「用户到底干了什么」。LVGL 里定义了二十多种事件,但日常开发中,80% 的场景只用其中五六个。我刚开始学 LVGL 时,总想把所有事件都记住,后来发现——记住核心的几个,剩下的查手册就行。
这一节,咱们就把最常用的几个事件掰开揉碎讲清楚。你想想看,一个按钮按下去,系统到底经历了什么?
2.1 事件的生命周期:一次点击的完整旅程
先看一张图。这是我个人习惯用的「事件触发时序图」,能帮你快速建立整体认知。
嗯,这里要注意:CLICKED 和 LONG_PRESSED 是互斥的。系统会先等待一段时间(默认 400ms),如果手指在 400ms 内抬起,触发 CLICKED;如果超过 400ms 还没抬起,触发 LONG_PRESSED,CLICKED 就不再触发了。
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);
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 则独立于这个流水线,由对象内部状态变化驱动。
好了,这一节的核心事件就讲到这里。你只要记住:CLICKED 是「确认」,PRESSING 是「持续反馈」,LONG_PRESSED 是「快捷操作」,VALUE_CHANGED 是「状态变化」。把这四个用好,90% 的交互场景都能覆盖。