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 事件流程可视化
下面这张图,把手指从按下到松开的完整事件流画出来了。你看一眼就明白:
嗯,这张图把整个流程串起来了。你注意看右侧的绿色和青色节点——只要手指不松、不滑动,REPEAT 就会一直循环下去,直到你松手触发 RELEASED。
3.6 什么时候该用哪个?
我根据项目经验,列了个对照表:
| 场景 | 推荐事件 | 原因 |
|---|---|---|
| 弹出上下文菜单 | LONG_PRESSED |
只需要触发一次,重复没意义 |
| 音量/亮度调节 | LONG_PRESSED_REPEAT |
需要连续增减,且速度要快 |
| 删除确认(长按删除) | LONG_PRESSED |
配合进度条动画,触发一次就够 |
| 游戏中的连击 | LONG_PRESSED_REPEAT |
按住持续攻击,重复间隔要短 |
我的建议:如果你不确定该用哪个,先只注册 LONG_PRESSED。等用户反馈「按住不放时能不能连续变化」,再补上 REPEAT。别一开始就把交互做复杂了。