2. 事件类型详解(上):LV_EVENT_CLICKED、LV_EVENT_PRESSED、LV_EVENT_RELEASED

好,咱们直接进入正题。上一章我们聊了事件驱动的基本概念,说白了就是「发生了什么事,然后去处理」。这一章,我把触摸事件里最常用的三个家伙拎出来,一个一个拆开看。

我个人习惯把这三个事件叫做「触摸三兄弟」:按下(PRESSED)、松开(RELEASED)、点击(CLICKED)。它们的关系很微妙,搞不清楚的话,你的按钮要么点不动,要么乱触发。我在项目里见过不少新手在这上面翻车。

核心认知:PRESSED 和 RELEASED 是「过程事件」,CLICKED 是「结果事件」。一个完整的点击,必然先 PRESSED,再 RELEASED,最后才可能 CLICKED。

2.1 LV_EVENT_PRESSED —— 手指刚碰上的那一刻

这个事件触发得最早。只要用户的手指(或触摸笔)碰到了屏幕上的对象,LV_EVENT_PRESSED 就会立刻被发送。注意,是「碰到」就触发,不需要等待任何时间或位移。

什么时候用?举个例子,我做的一个智能家居面板,用户按下灯光按钮时,我希望按钮立刻变暗,给一个「我被按了」的视觉反馈。这时候用 PRESSED 事件就非常合适。

static void btn_event_handler(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_PRESSED) {
        // 按下时,按钮变暗,给用户触感反馈
        lv_obj_set_style_bg_color(btn, lv_color_hex(0x666666), 0);
        lv_obj_set_style_opa(btn, LV_OPA_80, 0);
        LV_LOG_USER("按钮被按下");
    }
}
我的经验:PRESSED 事件里不要做耗时操作。比如读写文件、网络请求,这些会阻塞 UI 刷新,导致触摸反馈卡顿。用户会感觉「这屏幕是不是坏了?」。我曾经在一个项目里把 EEPROM 写入放在 PRESSED 里,结果每次按下去都要等 5ms,用户体验极差。

2.2 LV_EVENT_RELEASED —— 手指抬起来的那一瞬间

这个事件在用户手指离开屏幕时触发。注意,它不关心你手指抬起来之前做了什么——哪怕你按下去之后滑到了对象外面再松开,LV_EVENT_RELEASED 依然会触发。

为什么会这样?因为 LVGL 的设计逻辑是:只要你在该对象上按下去,松开时不管你在哪里,都会通知这个对象「我被松开了」。这跟 CLICKED 不一样,后面会讲。

我建议把 RELEASED 用于「松开后立即生效」的操作。比如音量滑块,用户按住拖动,松开时确认最终音量值。

if(code == LV_EVENT_RELEASED) {
    // 松开时,恢复按钮颜色
    lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), 0);
    lv_obj_set_style_opa(btn, LV_OPA_100, 0);
    LV_LOG_USER("按钮被松开");
}
避坑指南:我曾经在 RELEASED 里调用了 lv_obj_del() 删除对象本身。结果呢?对象被删了,但事件系统还在尝试处理后续事件,直接导致内存访问异常。记住:不要在事件回调里删除触发事件的对象。如果非要删,用 lv_event_send() 延迟处理,或者用 lv_async_call()。

2.3 LV_EVENT_CLICKED —— 真正的「点击」

这是大家最熟悉的事件。但它的触发条件比你想的要严格:

  1. 必须先在该对象上触发过 PRESSED
  2. 松开时手指必须在对象内部(或在一个可接受的滑动范围内)
  3. 没有触发长按、拖动等其他手势

说白了,CLICKED 是一个「干净利落的点击」。用户按下去,没有乱滑,没有拖拽,然后松开,这才算一次点击。

我见过有人把 CLICKED 和 RELEASED 混用,结果发现有时候按下去再滑出去松开,RELEASED 触发了但 CLICKED 没触发。嗯,这就是两者的本质区别。

if(code == LV_EVENT_CLICKED) {
    // 真正的点击事件
    LV_LOG_USER("按钮被点击,执行主要操作");
    // 比如切换页面、提交表单等
}

2.4 三者的关系与触发顺序

为了让你更直观地理解,我画了一张流程图。这张图展示了从手指触摸到松开,三个事件是如何依次产生的。

触摸三兄弟事件触发流程 用户触摸屏幕 LV_EVENT_PRESSED 触发 手指刚接触屏幕,立刻触发 手指移动/滑动? LVGL 内部检测滑动距离 是 触发拖动事件 CLICKED 取消 否 LV_EVENT_RELEASED 触发 手指离开屏幕,无论位置 LV_EVENT_CLICKED 触发

2.5 三个事件的对比表格

我把它们的核心差异整理成了一张表,方便你查阅和对比:

事件 触发时机 触发条件 典型用途 是否受滑动影响
PRESSED 手指接触屏幕瞬间 触摸点在对象区域内 视觉反馈、触觉反馈、开始计时 否(立即触发)
RELEASED 手指离开屏幕瞬间 之前在该对象上按过 恢复状态、确认操作、清理资源 否(总会触发)
CLICKED 手指离开屏幕后 按过 + 未滑动 + 松开时在区域内 主要操作、页面跳转、数据提交 是(滑动则取消)

2.6 实际项目中的组合用法

光讲理论没意思,我分享一个我在智能门锁项目里用过的实际场景。门锁的解锁按钮需要三种反馈:

  • 按下时:按钮颜色变深,同时蜂鸣器短响一声(PRESSED)
  • 松开时:按钮颜色恢复,蜂鸣器停止(RELEASED)
  • 点击成功时:执行解锁逻辑,显示解锁动画(CLICKED)
static void unlock_btn_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_PRESSED) {
        // 按下:视觉 + 声音反馈
        lv_obj_set_style_bg_color(btn, lv_color_hex(0x004d40), 0);
        buzzer_on();
    }
    else if(code == LV_EVENT_RELEASED) {
        // 松开:恢复外观,关闭蜂鸣器
        lv_obj_set_style_bg_color(btn, lv_color_hex(0x00796b), 0);
        buzzer_off();
    }
    else if(code == LV_EVENT_CLICKED) {
        // 点击成功:执行解锁
        door_unlock();
        lv_label_set_text(ui_status_label, "门已解锁");
    }
}
我的建议:不要把 CLICKED 当成 RELEASED 的替代品。如果你希望「只要松开就执行」,用 RELEASED;如果你希望「只有完整点击才执行」,用 CLICKED。这两个事件各司其职,混用会导致逻辑混乱。

2.7 常见误区总结

最后,我把新手最容易踩的几个坑列出来,你写代码的时候可以对照检查:

  • 误区一:在 PRESSED 里做耗时操作。这会导致触摸反馈延迟,用户会觉得屏幕卡顿。
  • 误区二:在 RELEASED 里删除触发事件的对象。这会导致野指针或内存泄漏。
  • 误区三:认为 CLICKED 一定会触发。记住,只要手指滑出对象区域,CLICKED 就不会触发,但 RELEASED 依然会。
  • 误区四:在 CLICKED 里判断触摸坐标。没必要,CLICKED 本身就保证了触摸点在对象内部。

嗯,这一章的内容就到这里。这三个事件是触摸交互的基石,搞清楚了它们,后面的长按、双击、拖动事件理解起来就轻松多了。