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 事件触发流程图

下面这张图帮你理清单击、双击、长按之间的关系。我画的时候特意把时间轴标出来了,方便理解。

触摸事件触发流程(单击 / 双击 / 长按) 用户按下 是否超过长按阈值? 是 LV_EVENT_LONG_PRESSED 否 用户抬起 是否在双击窗口内(300ms)? 是 LV_EVENT_DOUBLE_CLICKED 否 LV_EVENT_SHORT_CLICKED 注意:长按结束后 不会触发 SHORT_CLICKED

4.5 实际项目中的参数调优

LVGL 默认的单击、双击参数不一定适合所有场景。我整理了一份常用参数表,你可以根据产品类型调整:

参数 默认值 推荐范围 适用场景
长按触发时间 400ms 300~800ms 按钮、列表项
双击窗口时间 300ms 250~500ms 图片、卡片
滚动阈值 10px 5~20px 列表、滑动控件

我的经验:工业触摸屏的用户操作速度通常比手机慢,建议把双击窗口调到 400ms。消费类产品可以保持 300ms,响应更跟手。记得在产品设置里留一个「触摸灵敏度」选项,让用户自己调。

4.6 本章小结

今天的内容其实就三个核心点:

  • SHORT_CLICKED 是排除了长按的单击事件,适合做确认操作
  • DOUBLE_CLICKED 需要两次短按在窗口时间内完成,适合做快捷操作
  • 单击和双击同时存在时,必须用延时或状态机处理冲突

嗯,这些知识点看起来简单,但我在好几个项目里都踩过坑。尤其是双击和单击的冲突,如果你不做处理,用户操作起来会感觉「这 UI 是不是有毛病?」。下次遇到类似需求,直接拿今天的代码去用就行。