当前位置: 首页 > article >正文

LVGL源码(4):LVGL关于EVENT事件的响应逻辑

 LVGL版本:8.3


    前面我们说过了,LVGL实现大概分为三个步骤:检测用户输入操作、调用我们编写的逻辑、在屏幕上显示对应的画面;而第二步“我们编写的逻辑”就是通过EVENT事件回调函数实现的;下面给出一个简单的应用Event事件的代码:

lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, NULL);   /*Assign an event callback*/

...

static void my_event_cb(lv_event_t * event)
{
    printf("Clicked\n");
}

    我们可以看到,调用我们编写的逻辑即EVENT事件回调函数,我们只需要首先创建一个控件,然后写出对应的EVENT事件回调函数,最后通过struct _lv_event_dsc_t * lv_obj_add_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter,void * user_data)将控件、EVENT事件回调函数和外部操作三者绑定在一起即可,最后一项可选,是用户想向EVENT事件回调函数中传入的自定义数据指针。上面这个例子就是当btn控件受到外部操作为LV_EVENT_CLICKED时就调用my_event_cb()回调函数;

    那接下来我们就来看看lv_obj_add_event_cb()函数参数的数据类型和函数的内部实现:

lv_event.c:
struct _lv_event_dsc_t * lv_obj_add_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter,
                                             void * user_data)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_allocate_spec_attr(obj);

    obj->spec_attr->event_dsc_cnt++;
    obj->spec_attr->event_dsc = lv_mem_realloc(obj->spec_attr->event_dsc,
                                               obj->spec_attr->event_dsc_cnt * sizeof(lv_event_dsc_t));
    LV_ASSERT_MALLOC(obj->spec_attr->event_dsc);

    obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1].cb = event_cb;
    obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1].filter = filter;
    obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1].user_data = user_data;

    return &obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1];
}


lv_obj.h:
typedef struct _lv_obj_t {
    const lv_obj_class_t * class_p;
    struct _lv_obj_t * parent;
    _lv_obj_spec_attr_t * spec_attr;
    _lv_obj_style_t * styles;
#if LV_USE_USER_DATA
    void * user_data;
#endif
    lv_area_t coords;
    lv_obj_flag_t flags;
    lv_state_t state;
    uint16_t layout_inv : 1;
    uint16_t scr_layout_inv : 1;
    uint16_t skip_trans : 1;
    uint16_t style_cnt  : 6;
    uint16_t h_layout   : 1;
    uint16_t w_layout   : 1;
} lv_obj_t;

typedef struct {
    struct _lv_obj_t ** children;       /**< Store the pointer of the children in an array.*/
    uint32_t child_cnt;                 /**< Number of children*/
    lv_group_t * group_p;

    struct _lv_event_dsc_t * event_dsc; /**< Dynamically allocated event callback and user data array*/
    lv_point_t scroll;                  /**< The current X/Y scroll offset*/

    lv_coord_t ext_click_pad;           /**< Extra click padding in all direction*/
    lv_coord_t ext_draw_size;           /**< EXTend the size in every direction for drawing.*/

    lv_scrollbar_mode_t scrollbar_mode : 2; /**< How to display scrollbars*/
    lv_scroll_snap_t scroll_snap_x : 2;     /**< Where to align the snappable children horizontally*/
    lv_scroll_snap_t scroll_snap_y : 2;     /**< Where to align the snappable children vertically*/
    lv_dir_t scroll_dir : 4;                /**< The allowed scroll direction(s)*/
    uint8_t event_dsc_cnt;                  /**< Number of event callbacks stored in `event_dsc` array*/
} _lv_obj_spec_attr_t;


lv_event.c:
typedef struct _lv_event_dsc_t {
    lv_event_cb_t cb;
    void * user_data;
    lv_event_code_t filter : 8;
} lv_event_dsc_t;

    函数内部逻辑主要为:首先通过lv_obj_allocate_spec_attr(obj);函数判断这次绑定事件的控件的spec_attr指针是否为空,我们可以看到lv_obj_t数据类型中spec_attr是一个_lv_obj_spec_attr_t类型指针,这里判断没有为spec_attr分配地址的话,就创建一个_lv_obj_spec_attr_t大小的内存并让spec_attr指针指向该内存;接下来我们就开始将控件obj和EVENT事件回调函数以及外部操作以及用户数据指针绑定在一起,这里我们看到_lv_obj_spec_attr_t数据结构体中有一个struct _lv_event_dsc_t * event_dsc结构体指针,_lv_event_dsc_t数据类型中就包含EVENT事件回调函数和外部操作以及自定义数据指针,我们只需要将函数lv_obj_add_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter,void * user_data)的后三个输入参数和控件obj中的 lv_event_cb_t cb;void * user_data;和 lv_event_code_t filter : 8;一一赋值即可。

    但是我们看到lv_obj_add_event_cb函数中并不是简单的赋值,而是有一些额外的操作,这就要说到直接赋值的缺点了,那就是一个obj控件只能和一个EVENT事件回调函数、外部操作和用户数据指针绑定在一起。因此这里我们发现_lv_event_dsc_t * event_dsc结构体指针实际上是指向一个以_lv_event_dsc_t 数据类型的数据为元素的动态数组,这里通过event_dsc_cnt表示数组元素个数,每次控件obj新绑定一个EVENT事件回调函数event_dsc_cnt就加一,且用lv_mem_realloc函数来分配一个新的数组空间,新的数组内容在原数组基础上不变,空间比原数组大一个数组元素的大小;最后为新增的数组元素和函数输入参数一一绑定即可实现控件和EVENT事件回调函数等绑定在一起的功能;

    我们现在知道了控件已经和EVENT事件回调函数绑定在一起了,接下来我们就看看该回调函数是怎么通过控件被外部操作时得到触发的,这里我们以Keypad输入设备为例:

     我们在之前的indev.c中可以看到关于keypad输入设备的处理函数static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data),在该函数中就会判断按下的按键的键值,然后做一些对应处理,最后调用lv_group_send_data(g, data->key);函数将按下的键值通过lv_event_send()函数传递到要触发的lv_event_t e事件中;我们可以看到当前group组的focus对象也一并传入到了lv_event_t e中,而关键就在lv_event_send()函数中调用的 event_send_core()函数,该函数就是EVENT事件的响应的核心;在event_send_core 函数中响应传入的event,如果我们之前通过lv_obj_add_event_cb()函数将当前group组的focus对象控件和对应EVENT事件回调函数绑定了,就依次调用和该控件绑定的所有EVENT事件回调函数。

lv_group.c:
lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)
{
    lv_obj_t * act = lv_group_get_focused(group);
    if(act == NULL) return LV_RES_OK;

    if(lv_obj_has_state(act, LV_STATE_DISABLED)) return LV_RES_OK;

    return lv_event_send(act, LV_EVENT_KEY, &c);
}


lv_event.c:

lv_res_t lv_event_send(lv_obj_t * obj, lv_event_code_t event_code, void * param)
{
    if(obj == NULL) return LV_RES_OK;

    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_event_t e;
    e.target = obj;
    e.current_target = obj;
    e.code = event_code;
    e.user_data = NULL;
    e.param = param;
    e.deleted = 0;
    e.stop_bubbling = 0;
    e.stop_processing = 0;

    /*Build a simple linked list from the objects used in the events
     *It's important to know if this object was deleted by a nested event
     *called from this `event_cb`.*/
    e.prev = event_head;
    event_head = &e;

    /*Send the event*/
    lv_res_t res = event_send_core(&e);

    /*Remove this element from the list*/
    event_head = e.prev;

    return res;
}


static lv_res_t event_send_core(lv_event_t * e)
{
    EVENT_TRACE("Sending event %d to %p with %p param", e->code, (void *)e->current_target, e->param);

    /*Call the input device's feedback callback if set*/
    lv_indev_t * indev_act = lv_indev_get_act();
    if(indev_act) {
        if(indev_act->driver->feedback_cb) indev_act->driver->feedback_cb(indev_act->driver, e->code);
        if(e->stop_processing) return LV_RES_OK;
        if(e->deleted) return LV_RES_INV;
    }

    lv_res_t res = LV_RES_OK;
    lv_event_dsc_t * event_dsc = lv_obj_get_event_dsc(e->current_target, 0);

    uint32_t i = 0;
    while(event_dsc && res == LV_RES_OK) {
        if(event_dsc->cb  && ((event_dsc->filter & LV_EVENT_PREPROCESS) == LV_EVENT_PREPROCESS)
           && (event_dsc->filter == (LV_EVENT_ALL | LV_EVENT_PREPROCESS) ||
               (event_dsc->filter & ~LV_EVENT_PREPROCESS) == e->code)) {
            e->user_data = event_dsc->user_data;
            event_dsc->cb(e);

            if(e->stop_processing) return LV_RES_OK;
            /*Stop if the object is deleted*/
            if(e->deleted) return LV_RES_INV;
        }

        i++;
        event_dsc = lv_obj_get_event_dsc(e->current_target, i);
    }

    res = lv_obj_event_base(NULL, e);

    event_dsc = res == LV_RES_INV ? NULL : lv_obj_get_event_dsc(e->current_target, 0);

    i = 0;
    while(event_dsc && res == LV_RES_OK) {
        if(event_dsc->cb && ((event_dsc->filter & LV_EVENT_PREPROCESS) == 0)
           && (event_dsc->filter == LV_EVENT_ALL || event_dsc->filter == e->code)) {
            e->user_data = event_dsc->user_data;
            event_dsc->cb(e);

            if(e->stop_processing) return LV_RES_OK;
            /*Stop if the object is deleted*/
            if(e->deleted) return LV_RES_INV;
        }

        i++;
        event_dsc = lv_obj_get_event_dsc(e->current_target, i);
    }

    if(res == LV_RES_OK && e->current_target->parent && event_is_bubbled(e)) {
        e->current_target = e->current_target->parent;
        res = event_send_core(e);
        if(res != LV_RES_OK) return LV_RES_INV;
    }

    return res;
}

event_send_core函数执行步骤如下:

一、获取输入设备的句柄,调用 feedback_cb 来处理触摸,按键这些事件;

二、事件预处理:优先级高的事件处理。LV_EVENT_PREPROCESS标志位用于标记高优先级事件。用法类似lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_PREPROCESS | LV_EVENT_CLICKED, NULL);注意只有该标志位才能和其他事件描述符 |,事件描述符和事件描述符之间只能独立使用不能通过 |使用;

三、基础事件处理:对象的基类的事件处理,控件面向LVGL系统的事件、

四、事件回调:调用常规优先级的事件回调函数,即我们前面通过lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, NULL);绑定的my_event_cb函数;

五、事件冒泡:向父对象传递事件。如果当前 obj 是支持 Bubble 的话,那么它会向它的父对象 parent (如果有父对象)进行传递,直到某一个 parent 不支持 bubble 为止;

    至此我们知道了LVGL的EVENT事件响应是如何触发的了,本质就是通过输入设备的处理函数类似static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)在其中调用event_send_core函数,将当前发生的事件和控件等参数传入event_send_core进行处理;而LVGL的三大步骤:检测用户输入操作、调用我们编写的逻辑、在屏幕上显示对应的画面;我们也有了一个初步了解,知道LVGL大概是如何运行的了,后面有时间会讲一下keypad输入设备的导航态和编辑态以及一些控件的使用方法;

参考文章:

Events(事件) — 百问网LVGL中文教程文档 文档

LVGL (9) Event 机制实现_lvgl event-CSDN博客


http://www.kler.cn/a/472906.html

相关文章:

  • MATLAB深度学习实战文字识别
  • Vue进阶(贰幺叁)node 版本切换
  • 大数据-268 实时数仓 - ODS层 将 Kafka 中的维度表写入 DIM
  • patchwork++地面分割学习笔记
  • C++单例模式跨DLL调用问题梳理
  • nginx配置 - 资源参数配置(性能优化)
  • CAD批量打印可检索的PDF文件
  • Redis 性能优化:利用 MGET 和 Pipeline 提升效率
  • 软件测试的未来:如何跨越自动化到自主测试的鸿沟
  • 【深度学习系统】Lecture 4 - Automatic Differentiation
  • 左神算法基础巩固--4
  • ESP32 IDF VScode出现头文件“无法打开 源 文件 ”,并有红色下划线警告
  • Docker 容器运行后自动退出的解决方案
  • MySQL 分库分表实战(一)
  • 无网络时自动切换备用网络环境
  • C++二十三种设计模式之迭代器模式
  • Python爬虫基础——XPath表达式
  • ffmpeg之h264格式转yuv
  • WEBRTC前端播放 播放器组件封装
  • 【Linux】深入理解文件系统(超详细)
  • 自动化执行 SQL 脚本解决方案
  • 十六、Vue 组件
  • 《深入浅出HTTPS​​​​​​​​​​​​​​​​​》读书笔记(26):数字签名
  • 【数据结构-堆】【二分】力扣3296. 移山所需的最少秒数
  • 牛客网刷题 ——C语言初阶(5操作符)——BC90 矩阵计算
  • 解决word桌面图标空白