3. 长按与重复触发:LV_EVENT_LONG_PRESSED 和 LV_EVENT_LONG_PRESSED_REPEAT

上一章我们聊了点击和释放的基础事件。今天要讲的这两个事件,说白了就是「按住了别松手」——但这里面的门道,比你想的要多。

我记得刚接触 LVGL 时,以为长按就是设个延时然后触发一次。结果做出来的 UI 要么反应迟钝,要么误触频繁。后来翻源码才发现,LVGL 的长按机制其实分两个阶段:触发和重复。搞懂这个,你的交互才能「跟手」。

3.1 两个事件的区别

先看官方定义:

事件 触发时机 典型用途
LV_EVENT_LONG_PRESSED 手指按住超过预设时间(默认 300ms),且未滑动 弹出菜单、显示详情、进入编辑模式
LV_EVENT_LONG_PRESSED_REPEAT 长按触发后,每隔固定间隔(默认 100ms)持续触发 数值增减、滚动加速、连续操作

简单说:LONG_PRESSED 只响一次,LONG_PRESSED_REPEAT 会一直响。你想想看,音量键按着不放的时候,是不是先「滴」一声,然后「滴滴滴」连续响?前者就是 LONG_PRESSED,后者就是 REPEAT。

核心要点:两个事件是先后关系,不是并列关系。必须先触发 LONG_PRESSED,才会开始 REPEAT。如果手指在长按过程中滑出了对象区域,两个事件都不会触发。

3.2 时间参数怎么调?

LVGL 提供了两个全局参数,在 lv_conf.h 里配置:

/* 长按触发时间(单位:ms) */
#define LV_INDEV_DEF_LONG_PRESS_TIME   400

/* 长按重复间隔(单位:ms) */
#define LV_INDEV_DEF_LONG_PRESS_REP_TIME  100

我个人习惯把触发时间设在 300~500ms 之间。太短容易误触,太长用户会觉得「这设备是不是坏了」。重复间隔我一般用 80~120ms,太快了数值跳得飞起,太慢了又不够「连续」的感觉。

小技巧:如果你只想用 REPEAT 而不用 LONG_PRESSED,可以在 LONG_PRESSED 的回调里什么都不做,只处理 REPEAT。但别忘了,REPEAT 依赖 LONG_PRESSED 先触发,所以两个事件都得注册。

3.3 代码实战:做一个「长按加速」的计数器

下面这个例子,我曾在智能家居面板上用过。长按温度调节按钮,数值会先慢后快:

static void btn_counter_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);
    lv_obj_t * label = lv_obj_get_child(btn, 0);

    static int count = 0;

    if(code == LV_EVENT_LONG_PRESSED) {
        /* 第一次长按触发,慢速增加 */
        count += 1;
        lv_label_set_text_fmt(label, "%d", count);
        lv_obj_set_style_bg_color(btn, lv_color_hex(0xFFA500), 0);
    }
    else if(code == LV_EVENT_LONG_PRESSED_REPEAT) {
        /* 重复触发,快速增加 */
        count += 3;
        lv_label_set_text_fmt(label, "%d", count);
        lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF4500), 0);
    }
    else if(code == LV_EVENT_RELEASED) {
        /* 松手恢复颜色 */
        lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), 0);
    }
}

/* 创建按钮 */
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 60);
lv_obj_center(btn);
lv_obj_add_event_cb(btn, btn_counter_event_cb, LV_EVENT_ALL, NULL);

lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "0");
lv_obj_center(label);

这段代码里,我故意让 LONG_PRESSED 只加 1,REPEAT 加 3。为什么?因为用户第一次长按时,往往只是想微调;按住不放才说明「我要大范围调整」。这种「先慢后快」的交互,比匀速增减舒服得多。

注意:长按事件和滑动事件是互斥的。如果手指在长按过程中移动超过 LV_INDEV_DEF_SCROLL_LIMIT(默认 50px),LVGL 会判定为滑动,长按事件立即取消。所以如果你的按钮需要同时支持长按和拖动,得小心处理事件优先级。

3.4 避坑指南:我曾经踩过的三个坑

  • 坑一:重复触发太快导致界面卡顿。 我曾经在 REPEAT 回调里做了大量 UI 刷新操作,结果 100ms 一次根本扛不住。后来把刷新逻辑改成「每 3 次重复才刷新一次」,或者用 lv_anim 做平滑过渡,问题就解决了。
  • 坑二:忘记处理 RELEASED 事件。 长按松手后,按钮颜色还停留在「被按住」的状态。用户会以为按钮卡住了。所以一定要在 RELEASED 里恢复样式。
  • 坑三:全局参数改完没生效。 记得 lv_conf.h 修改后要重新编译整个工程。我试过只改文件没编译,调试了半天才发现参数根本没变。

3.5 事件流程可视化

下面这张图,把手指从按下到松开的完整事件流画出来了。你看一眼就明白:

长按事件触发流程 手指按下 是否滑动超过阈值? 触发滑动事件 等待 LONG_PRESS_TIME LV_EVENT_LONG_PRESSED LV_EVENT_LONG_PRESSED_REPEAT(循环) 手指未松开且未滑动 每 REP_TIME 触发一次

嗯,这张图把整个流程串起来了。你注意看右侧的绿色和青色节点——只要手指不松、不滑动,REPEAT 就会一直循环下去,直到你松手触发 RELEASED。

3.6 什么时候该用哪个?

我根据项目经验,列了个对照表:

场景 推荐事件 原因
弹出上下文菜单 LONG_PRESSED 只需要触发一次,重复没意义
音量/亮度调节 LONG_PRESSED_REPEAT 需要连续增减,且速度要快
删除确认(长按删除) LONG_PRESSED 配合进度条动画,触发一次就够
游戏中的连击 LONG_PRESSED_REPEAT 按住持续攻击,重复间隔要短

我的建议:如果你不确定该用哪个,先只注册 LONG_PRESSED。等用户反馈「按住不放时能不能连续变化」,再补上 REPEAT。别一开始就把交互做复杂了。