一、简介前面介绍的东西都比较简单这篇文章介绍点有深度的东西。二、什么是Objects在LVGL中用户界面的 基本组成部分 是对象控件也称为 Widgets。例如一个 按钮、标签、图像、列表、图表 或者 文本区域。所有的对象都使用 lv_obj_t 指针作为句柄进行引用。之后可以使用该指针来设置或获取对象的属性。lv_obj_t 是 LVGL 中 所有 UI 对象的基类可以理解为万能对象。所有控件Label、Button、Slider...在底层都是 lv_obj_t 只是各自附加了不同的属性和行为。属性一个 lv_obj_t 内部包含这些通用属性属性说明设置函数位置x, y 坐标lv_obj_set_pos()尺寸宽度、高度lv_obj_set_size() / lv_obj_set_width()样式背景色、边框、圆角、字体等lv_obj_set_style_*()状态默认、按下、选中、禁用等lv_obj_add_state() / lv_obj_clear_state()事件点击、值改变、滚动等lv_obj_add_event_cb()父子关系每个对象有且只有一个父对象创建时指定标志可点击、可滚动、隐藏等lv_obj_add_flag()布局Flex、Gridlv_obj_set_flex_flow() / lv_obj_set_layout()特殊属性不同的对象有特定的属性例如滑块对象有最大值、最小值等等。后续用到特地给对象的时候再详细说。lv_obj_t (基类) ├── 位置、尺寸、样式、事件、父子关系...基类属性 │ └── 各种控件继承 lv_obj_t再附加自己的属性 ├── Label : lv_obj_t text文本内容 ├── Button : lv_obj_t 按下效果样式 ├── Slider : lv_obj_t value、range、拖拽逻辑 ├── Image : lv_obj_t src图片源 └── ...父子关系一个父对象可以被视为其子对象的容器。每个对象都都必须会有且仅有一个父对象屏幕除外但一个父对象可以有任意数量的子对象。lv_obj_t * screen lv_screen_active(); // 屏幕根 lv_obj_t * cont lv_obj_create(screen); // 容器父亲是 screen lv_obj_t * label lv_label_create(cont); // Label父亲是 cont lv_obj_t * btn lv_button_create(cont); // Button父亲是 cont lv_obj_t * btn_lbl lv_label_create(btn); // 按钮里的文字父亲是 btn screen └── cont ├── label └── btn └── btn_lbl父子关系带来的特性特性说明继承位置子对象坐标相对于父对象移动父对象子对象跟着动裁剪子对象超出父对象边界默认被裁剪继承透明度父对象半透明子对象也跟着半透明事件冒泡子对象的事件可以冒泡到父对象可选生命周期删除父对象时所有子对象自动删除lv_obj_t来看一下结构体lv_obj_t。typedef struct _lv_obj_t lv_obj_t;struct _lv_obj_t { const lv_obj_class_t * class_p; // 类信息决定这是 Label/Button/Slider... lv_obj_t * parent; // 父对象指针 lv_obj_spec_attr_t * spec_attr; // 特殊属性事件回调、子对象列表等懒加载 lv_obj_style_t * styles; // 样式列表 void * user_data; // 用户自定义数据 lv_area_t coords; // 坐标x1, y1, x2, y2 lv_obj_flag_t flags; // 标志位可点击、隐藏、可滚动... uint16_t state; // 状态默认、按下、选中、禁用... // 各种 1bit 标志 uint16_t layout_inv : 1; // 布局需要重新计算 uint16_t style_cnt : 6; // 样式数量 uint16_t h_layout : 1; // 高度由布局决定 uint16_t w_layout : 1; // 宽度由布局决定 uint16_t is_deleting : 1; // 正在删除中防止重复删除 uint16_t rendered : 1; // 是否已渲染过至少一次 // ... };lv_obj_t 只是个前向声明的别名让你在代码里用 lv_obj_t * 而不需要知道内部细节。真正的结构体内容在 _lv_obj_t 里定义在 lv_obj_private.h 注意文件名带private。为什么要这样设计设计目标实现方式信息隐藏公开头文件只暴露 typedef struct _lv_obj_t lv_obj_t; 看不到字段ABI 稳定用户代码只持有指针不依赖结构体大小LVGL 升级时字段变动不影响用户代码封装强制用户通过 API 函数 lv_obj_set_pos() 等操作对象不能直接读写字段关键字段说明字段类型作用对应 APIclass_pconst lv_obj_class_t *类信息决定这是哪种控件。Label 和 Button 的区别就在这里内部使用parentlv_obj_t *父对象形成对象树lv_obj_get_parent()spec_attrlv_obj_spec_attr_t *特殊属性按需分配包含事件回调、子对象链表、滚动信息等lv_obj_allocate_spec_attr()styleslv_obj_style_t *样式数组lv_obj_set_style_*()coordslv_area_t坐标区域 (x1,y1,x2,y2)由它推算出位置和尺寸lv_obj_get_pos()/lv_obj_get_width()flagslv_obj_flag_t标志位可点击、可滚动、隐藏等lv_obj_add_flag()/lv_obj_clear_flag()stateuint16_t当前状态DEFAULT/PRESSED/CHECKED/DISABLED...lv_obj_add_state()三、实例醒醒、醒醒。是不是看概念久了都快睡着了。那我们来个实例看看。先看代码/* * 父子关系演示 Demo * * 展示 LVGL 对象树的两个核心特性 * 1. 子对象坐标相对于父对象移动父对象子对象跟着动 * 2. 删除父对象时所有子对象自动被删除 * */ /* “移动父容器”按钮回调让父容器水平移动子对象跟着动 */ static void move_parent_cb(lv_event_t * e) { lv_obj_t * parent lv_event_get_user_data(e); int32_t x lv_obj_get_x(parent); x (x 30) % 300; lv_obj_set_x(parent, x); } /* “删除父容器”按钮回调删除父容器所有子对象自动消失 */ static void del_parent_cb(lv_event_t * e) { lv_obj_t * parent lv_event_get_user_data(e); lv_obj_del(parent); } static void create_parent_child_demo(void) { /* 获取当前活动屏幕对象后续所有控件都创建在它上面 */ lv_obj_t * screen lv_screen_active(); /* 设置屏幕的背景色为浅蓝灰色调亮等级4看起来比纯白柔和 */ lv_obj_set_style_bg_color(screen, lv_palette_lighten(LV_PALETTE_BLUE_GREY, 4), 0); /* * 创建父容器一个空盒子用来装子对象 * 参数 screen 表示父容器的父亲是屏幕 * */ lv_obj_t * parent lv_obj_create(screen); /* 设置父容器的尺寸宽 300 像素高 200 像素 */ lv_obj_set_size(parent, 300, 200); /* 设置父容器的位置相对于屏幕左上角 (100, 50) */ lv_obj_set_pos(parent, 100, 50); /* 设置父容器的背景色为浅蓝色 */ lv_obj_set_style_bg_color(parent, lv_palette_lighten(LV_PALETTE_BLUE, 4), 0); /* 设置父容器的边框颜色为蓝色 */ lv_obj_set_style_border_color(parent, lv_palette_main(LV_PALETTE_BLUE), 0); /* 设置父容器的边框宽度为 2 像素 */ lv_obj_set_style_border_width(parent, 2, 0); /* * 创建父容器顶部的标题标签 * 参数 parent 表示标签的父亲是父容器 * */ lv_obj_t * title lv_label_create(parent); /* 设置标题文本内容 */ lv_label_set_text(title, I am PARENT); /* 设置标题文字颜色为蓝色 */ lv_obj_set_style_text_color(title, lv_palette_main(LV_PALETTE_BLUE), 0); /* 让标题对齐到父容器的顶部居中位置偏移 (0, 5) */ lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5); /* * 创建子对象 1普通对象不是按钮 * 用不同背景色区分移动时视觉效果明显 * */ lv_obj_t * child1 lv_obj_create(parent); /* 父亲是 parent */ lv_obj_set_pos(child1, 20, 40); /* 相对父容器 (20, 40) */ lv_obj_set_size(child1, 80, 40); /* 尺寸 80x40 */ lv_obj_set_style_bg_color(child1, lv_palette_main(LV_PALETTE_GREEN), 0); /* 绿色背景 */ lv_obj_set_style_border_width(child1, 0, 0); /* 无边框 */ lv_obj_set_style_pad_all(child1, 0, 0); /* 无内边距 */ /* 在子对象 1 内部创建一个标签父亲是 child1 */ lv_obj_t * lbl1 lv_label_create(child1); lv_label_set_text(lbl1, Child 1); lv_obj_set_style_text_color(lbl1, lv_color_white(), 0); /* 白色文字 */ lv_obj_center(lbl1); /* 居中 */ /* * 创建子对象 2结构同上用不同颜色 * */ lv_obj_t * child2 lv_obj_create(parent); /* 父亲是 parent */ lv_obj_set_pos(child2, 110, 40); /* 相对父容器 (110, 40) */ lv_obj_set_size(child2, 80, 40); /* 尺寸 80x40 */ lv_obj_set_style_bg_color(child2, lv_palette_main(LV_PALETTE_ORANGE), 0); /* 橙色背景 */ lv_obj_set_style_border_width(child2, 0, 0); /* 无边框 */ lv_obj_set_style_pad_all(child2, 0, 0); /* 无内边距 */ lv_obj_t * lbl2 lv_label_create(child2); /* 子对象 2 内创建标签 */ lv_label_set_text(lbl2, Child 2); /* 设置文字 */ lv_obj_set_style_text_color(lbl2, lv_color_white(), 0); /* 白色文字 */ lv_obj_center(lbl2); /* 居中 */ /* * 操作按钮区直接创建在屏幕上不在父容器内 * */ /* 创建“移动父容器”按钮父亲是 screen */ lv_obj_t * move_btn lv_button_create(screen); lv_obj_set_pos(move_btn, 100, 290); /* 相对屏幕 (100, 290) */ lv_obj_set_size(move_btn, 120, 40); /* 尺寸 120x40 */ lv_obj_t * move_lbl lv_label_create(move_btn); /* 按钮内创建标签 */ lv_label_set_text(move_lbl, Move Parent); /* 设置文字 */ lv_obj_center(move_lbl); /* 居中 */ /* 绑定点击事件传入 parent 作为 user_data回调中通过它移动父容器 */ lv_obj_add_event_cb(move_btn, move_parent_cb, LV_EVENT_CLICKED, parent); /* 创建“删除父容器”按钮结构同上 */ lv_obj_t * del_btn lv_button_create(screen); /* 父亲是 screen */ lv_obj_set_pos(del_btn, 240, 290); /* 相对屏幕 (240, 290) */ lv_obj_set_size(del_btn, 120, 40); /* 尺寸 120x40 */ lv_obj_t * del_lbl lv_label_create(del_btn); /* 按钮内创建标签 */ lv_label_set_text(del_lbl, Del Parent); /* 设置文字 */ lv_obj_center(del_lbl); /* 居中 */ /* 绑定点击事件传入 parent 作为 user_data回调中通过它删除父容器 */ lv_obj_add_event_cb(del_btn, del_parent_cb, LV_EVENT_CLICKED, parent); } int main(void) { /* 初始化 LVGL 核心库 */ lv_init(); /* ---- 创建显示设备 ---- */ /* lv_sdl_window_create() 内部会调用 SDL_Init 初始化 SDL * 创建窗口注册刷新定时器并设置 lv_tick */ lv_display_t * display lv_sdl_window_create(500, 500); if(display NULL) { return 1; } /* ---- 创建输入设备 ---- */ /* 鼠标是必须的否则无法与窗口交互 */ lv_sdl_mouse_create(); lv_log_register_print_cb(log_cb); create_parent_child_demo(); while(1) { lv_timer_handler(); lv_delay_ms(5); } return 0; }可以看到点击“Move Parent”后回调中执行了设置parent的位置但是在parent下挂载的其他几个obj全都移动了。static void move_parent_cb(lv_event_t * e) { lv_obj_t * parent lv_event_get_user_data(e); int32_t x lv_obj_get_x(parent); x (x 30) % 300; lv_obj_set_x(parent, x); }点击“Del Parent”后只是删除parent但是挂载在parent下的多个obj也都跟着被删掉了。static void del_parent_cb(lv_event_t * e) { lv_obj_t * parent lv_event_get_user_data(e); lv_obj_del(parent); }