4. 事件类型详解(下):LV_EVENT_SHORT_CLICKED 与 LV_EVENT_DOUBLE_CLICKED
上一节我们把长按、短按的底层逻辑捋了一遍。今天聊的两个事件,其实更贴近用户日常操作——单击和双击。
你想想看,手机上的双击点赞、桌面上的双击打开文件,这些交互在 LVGL 里怎么实现?说白了,就是靠 LV_EVENT_SHORT_CLICKED 和 LV_EVENT_DOUBLE_CLICKED 这两个事件。
我个人习惯把这两个事件放在一起讲,因为它们经常「打架」。嗯,这里要注意,处理不好就会出 bug。
4.1 LV_EVENT_SHORT_CLICKED:短按单击
LV_EVENT_SHORT_CLICKED 触发条件很简单:用户在短时间内按下并释放,没有触发长按。它和 LV_EVENT_CLICKED 的区别在于——CLICKED 只要按下再抬起就算,而 SHORT_CLICKED 明确排除了长按场景。
核心区别:
LV_EVENT_CLICKED:按下 + 抬起,不管时间长短LV_EVENT_SHORT_CLICKED:按下 + 抬起,且时间小于长按阈值
我在项目中遇到过一个问题:按钮既要支持长按连续触发,又要支持单击确认。如果只用 CLICKED,长按结束后也会触发一次单击,逻辑就乱了。换成 SHORT_CLICKED 后,问题迎刃而解。
4.1.1 典型用法
static void btn_event_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_SHORT_CLICKED) {
LV_LOG_USER("短按单击触发");
// 执行单击操作
lv_label_set_text(label, "单击");
}
}
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_SHORT_CLICKED, NULL);
这段代码看起来简单,但有个坑——如果你同时注册了 LV_EVENT_LONG_PRESSED 和 LV_EVENT_SHORT_CLICKED,长按结束后不会触发 SHORT_CLICKED,这是 LVGL 帮你过滤好的。
4.2 LV_EVENT_DOUBLE_CLICKED:双击识别
双击事件稍微复杂一点。它需要检测两次连续的短按,而且两次点击的时间间隔不能超过系统设定的阈值。
LVGL 内部是怎么判断的?它维护了一个计时器:
- 第一次按下抬起后,开始计时
- 如果在
LV_INDEV_DEF_SCROLL_TIMEOUT时间内(默认 300ms)检测到第二次按下抬起,就触发DOUBLE_CLICKED - 如果超时,则只触发一次
SHORT_CLICKED
小技巧:双击的判定时间可以通过 lv_indev_set_long_press_time() 间接调整。虽然这个函数名字叫「长按时间」,但它也影响双击窗口。我一般设为 400ms,用户操作起来更从容。
4.2.1 双击代码示例
static void img_double_click_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_DOUBLE_CLICKED) {
LV_LOG_USER("双击图片");
// 放大图片或打开预览
lv_obj_set_style_transform_scale(img, 200, 0);
}
}
lv_obj_add_event_cb(img, img_double_click_cb, LV_EVENT_DOUBLE_CLICKED, NULL);
我曾经在一个相册应用里用这个事件做图片双击放大。一开始没注意,双击和单击事件同时注册,结果每次双击都会先触发一次单击,再触发双击。用户明明想放大,结果先执行了单击的返回操作……
避坑指南:如果你同时需要单击和双击,一定要在单击回调里加延时判断,或者用状态机区分。我曾经因为没处理这个,被测试同事追着改了三版。
4.3 单击与双击的冲突处理
这是实际项目中最容易出问题的地方。一个按钮既要有单击功能,又要有双击功能,怎么设计?
我推荐两种方案:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 延时判断法 | 单击事件不立即执行,等待双击窗口过期后再触发 | 逻辑清晰,代码简单 | 单击响应有延迟(300ms) |
| 状态机法 | 用变量记录点击次数,在定时器中判断最终状态 | 响应灵活,可扩展 | 代码量稍大 |
我个人更倾向延时判断法,因为嵌入式设备上用户对双击的容忍度较高,300ms 的单击延迟几乎感觉不到。
4.3.1 延时判断法实现
static lv_timer_t * click_timer = NULL;
static int click_count = 0;
static void click_timer_cb(lv_timer_t * t) {
if(click_count == 1) {
// 只有一次点击,执行单击操作
lv_label_set_text(label, "单击");
}
click_count = 0;
lv_timer_del(click_timer);
click_timer = NULL;
}
static void btn_event_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_SHORT_CLICKED) {
click_count++;
if(click_count == 1) {
// 第一次点击,启动定时器等待双击
click_timer = lv_timer_create(click_timer_cb, 300, NULL);
lv_timer_set_repeat_count(click_timer, 1);
} else if(click_count == 2) {
// 第二次点击,取消定时器,执行双击
lv_timer_del(click_timer);
click_timer = NULL;
click_count = 0;
lv_label_set_text(label, "双击");
}
}
}
这段代码我用了很多项目,基本没出过问题。注意定时器只执行一次,第二次点击时及时删除定时器,避免内存泄漏。
4.4 事件触发流程图
下面这张图帮你理清单击、双击、长按之间的关系。我画的时候特意把时间轴标出来了,方便理解。
4.5 实际项目中的参数调优
LVGL 默认的单击、双击参数不一定适合所有场景。我整理了一份常用参数表,你可以根据产品类型调整:
| 参数 | 默认值 | 推荐范围 | 适用场景 |
|---|---|---|---|
| 长按触发时间 | 400ms | 300~800ms | 按钮、列表项 |
| 双击窗口时间 | 300ms | 250~500ms | 图片、卡片 |
| 滚动阈值 | 10px | 5~20px | 列表、滑动控件 |
我的经验:工业触摸屏的用户操作速度通常比手机慢,建议把双击窗口调到 400ms。消费类产品可以保持 300ms,响应更跟手。记得在产品设置里留一个「触摸灵敏度」选项,让用户自己调。
4.6 本章小结
今天的内容其实就三个核心点:
SHORT_CLICKED是排除了长按的单击事件,适合做确认操作DOUBLE_CLICKED需要两次短按在窗口时间内完成,适合做快捷操作- 单击和双击同时存在时,必须用延时或状态机处理冲突
嗯,这些知识点看起来简单,但我在好几个项目里都踩过坑。尤其是双击和单击的冲突,如果你不做处理,用户操作起来会感觉「这 UI 是不是有毛病?」。下次遇到类似需求,直接拿今天的代码去用就行。