欢迎光临
我们一直在努力

LVGL 面试实战:对象模型及继承机制

一、场景引入

在 LVGL 项目开发中,你是否遇到过自定义控件无法复用基础控件的事件回调、修改样式不生效、或者调用通用控件 API 触发异常的问题?这些问题 90% 都是对 LVGL 对象模型和继承机制理解不到位导致的。该考点是 LVGL 二次开发、自定义控件的核心基础,也是面试中区分只会调用 API 和能深度定制 LVGL 开发人员的关键考点。

二、原理解析

LVGL 采用面向对象的设计思想,基于 C 语言实现了单继承的对象模型,核心原理如下:

  • 基础对象结构:所有控件的根类是lv_obj_t,内部包含坐标、大小、样式列表、事件回调、父对象指针、子对象链表、类指针lv_obj_class_t *class_p等通用属性,任何控件实例的首地址都可以强制转为lv_obj_t*访问通用属性。
  • 类结构lv_obj_class_t:每个控件类对应一个全局的lv_obj_class_t实例,包含父类指针base_class、类实例大小、构造函数、析构函数、事件处理函数、用户数据等字段,继承关系通过父类指针串联,最终都指向lv_obj_class根类。
  • 继承实现机制:子类实例的内存布局是「父类实例结构 + 子类特有属性」,创建子类对象时会先调用父类构造函数初始化父类属性,再调用子类构造函数初始化特有属性;调用 API 时先判断传入对象的类继承关系合法性,再通过类的事件处理链从子类到父类逐层传递事件 / 属性操作。
  • 多态支持:通过重写类的事件处理回调、虚函数表(LVGL v8 + 引入部分虚函数支持)实现子类对父类行为的覆盖,通用 API(如lv_obj_set_pos、lv_obj_add_event_cb)对所有子类都适用,符合面向对象的多态特性。
  • 三、三道递进追问及答案

    追问 1:基础追问:lv_btn_t 类型的指针可以直接传给 lv_obj_set_size 函数吗?为什么?

    可以。因为 lv_btn_t 的内存布局首地址就是父类 lv_obj_t 的实例结构,所有 LVGL 控件都继承自 lv_obj_t,因此任何控件指针都可以安全强制转为 lv_obj_t * 传入通用 API,LVGL 内部也会做类继承关系校验保证合法性。

    追问 2:进阶变形:自定义一个继承自 lv_obj_t 的圆角矩形控件,需要实现哪些关键步骤?

  • 定义子类结构体,首成员为 lv_obj_t,后面加自定义属性;
  • 定义全局 lv_obj_class_t 实例,指定 base_class 为 & lv_obj_class,设置实例大小、构造 / 析构函数、事件处理回调;
  • 实现构造函数,给控件设置默认圆角样式等初始属性;
  • 实现事件处理回调,处理绘制、尺寸调整等子类特有的事件,不需要重写的事件需传递给父类处理;
  • 封装对象创建 API,内部调用 lv_obj_class_create_obj 生成实例。
  • 追问 3:实战综合:为什么自定义控件重写了事件回调后,父类的默认绘制行为消失了?怎么解决?

    因为 LVGL 的事件处理是链式传递,如果子类回调里直接返回 LV_EVENT_RESULT_OK 而不继续传递事件,父类的事件处理函数就不会被执行。解决方法是子类处理完自身事件后,调用 lv_obj_event_base (class_p->base_class, obj, e) 将事件传递给父类处理,或者只拦截需要重写的事件,其他事件默认传递。

    四、最小验证实验

    4.1 实验环境

    LVGL v8.3.x 仿真环境(PC 端 SDL 仿真、或 ESP32/STM32 LVGL 工程均可,这里采用 PC 端 SDL 仿真环境)

    4.2 实验代码

    #include "lvgl/lvgl.h"

    // 1. 定义自定义圆角矩形类的私有结构
    typedef struct {
    lv_obj_t base; // 首成员必须是父类结构,实现继承
    lv_color_t fill_color; // 自定义属性:填充色
    } my_round_rect_t;

    // 2. 类事件处理回调
    static void my_round_rect_event(const lv_obj_class_t * class_p, lv_event_t * e)
    {
    lv_event_code_t code = lv_event_get_code(e);
    my_round_rect_t * obj = (my_round_rect_t *)lv_event_get_current_target(e);

    if(code == LV_EVENT_DRAW_MAIN) {
    // 子类特有绘制逻辑:输出自定义颜色信息
    LV_LOG_USER("Custom rect draw, fill color: 0x%06X", lv_color_to_hex(obj->fill_color));
    }

    // 调用父类的事件处理,保留基础控件的行为
    lv_obj_event_base(class_p->base_class, e->current_target, e);
    }

    // 3. 类构造函数
    static void my_round_rect_constructor(const lv_obj_class_t * class_p, void * obj)
    {
    LV_UNUSED(class_p);
    my_round_rect_t * rect = (my_round_rect_t *)obj;
    // 初始化默认属性:圆角半径10,默认填充蓝色
    lv_obj_set_style_radius((lv_obj_t *)rect, 10, LV_PART_MAIN);
    rect->fill_color = lv_color_hex(0x0088FF);
    lv_obj_set_style_bg_color((lv_obj_t *)rect, rect->fill_color, LV_PART_MAIN);
    }

    // 4. 定义全局类实例,指定继承关系
    const lv_obj_class_t my_round_rect_class = {
    .base_class = &lv_obj_class, // 父类是lv_obj_t
    .instance_size = sizeof(my_round_rect_t), // 实例内存大小
    .constructor_cb = my_round_rect_constructor, // 构造函数
    .event_cb = my_round_rect_event, // 事件回调
    .name = "MyRoundRect", // 类名,调试用
    };

    // 5. 封装创建API
    lv_obj_t * my_round_rect_create(lv_obj_t * parent)
    {
    return lv_obj_class_create_obj(&my_round_rect_class, parent);
    }

    // 测试入口
    void lv_custom_obj_test(void)
    {
    // 创建自定义圆角矩形
    lv_obj_t * rect = my_round_rect_create(lv_scr_act());
    // 调用父类通用API设置尺寸和位置,验证继承有效性
    lv_obj_set_size(rect, 200, 100);
    lv_obj_center(rect);

    // 验证类继承关系
    bool is_obj = lv_obj_check_type(rect, &lv_obj_class);
    bool is_my_class = lv_obj_check_type(rect, &my_round_rect_class);
    LV_LOG_USER("Is base lv_obj_t: %d, Is MyRoundRect: %d", is_obj, is_my_class);
    }

    4.3 操作步骤

  • 将上述代码添加到 LVGL 仿真工程的测试文件中;
  • 在 main 函数的 lv_init ()、显示驱动初始化完成后,调用 lv_custom_obj_test ();
  • 编译运行仿真工程,查看日志输出和界面显示。
  • 4.4 预期结果

  • 界面中央显示一个蓝色圆角矩形,说明父类 lv_obj_t 的通用属性、样式系统正常工作;
  • 日志输出:Is base lv_obj_t: 1, Is MyRoundRect: 1,证明继承关系校验通过;
  • 日志还会输出Custom rect draw, fill color: 0x0088FF,证明子类的事件回调正常执行,且父类的默认绘制行为被保留。
  • 五、实战总结

  • 核心特性:LVGL 基于 C 语言实现单继承对象模型,所有控件首地址均可安全转为lv_obj_t*传入通用 API。
  • 继承实现:类的继承通过lv_obj_class_t的base_class指针串联,子类内存布局严格遵循「父类结构 + 子类特有属性」规则。
  • 自定义控件核心流程:定义子类结构→声明全局类实例→实现构造 / 析构函数→实现事件回调(必须传递未处理事件到父类)→封装创建 API。
  • 易错点:子类结构体首成员未声明为父类结构会导致内存布局错乱;事件回调未传递给父类会导致基础绘制、响应行为丢失。
  • 赞(0)
    未经允许不得转载:171主机测评 » LVGL 面试实战:对象模型及继承机制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址