【奶茶Beta专项】【LVGL9.4源码分析】09-core-obj核心对象系统
- 1 概述
- 1.1 文档目的
- 1.2 代码版本与范围
- 2 设计意图与总体定位
- 2.1 `lv_obj_t` 在 LVGL 中扮演的角色
- 2.2 对象内部结构的关键字段
- 2.3 对象生命周期与对象树
- 3 使用方式与典型场景
- 3.1 创建对象与构建对象树
- 3.2 标志位(flags)与状态(states)的常见用法
- 3.3 几何、布局与滚动的典型组合场景
- 4 接口分类与 API 速查表
- 4.1 创建与基础信息相关接口
- 4.2 标志位(flags)与状态(states)管理
- 4.2.1 Flags 操作接口
- 4.2.2 状态(states)操作接口
- 4.3 对象树与层级操作(`lv_obj_tree.*`)
- 4.3.1 删除与清理
- 4.3.2 父子关系与顺序调整
- 4.3.3 查询屏幕、display、父子/兄弟与子节点计数
- 4.3.4 名称与查找(在启用 `LV_USE_OBJ_NAME` 时)
- 4.4 几何与布局相关接口(`lv_obj_pos.*`)
- 4.4.1 位置与尺寸
- 4.4.2 布局与对齐
- 4.4.3 坐标与变换
- 4.5 滚动与滚动条相关接口(`lv_obj_scroll.*`)
- 4.5.1 滚动配置
- 4.5.2 滚动状态查询
- 4.5.3 滚动控制与可见性
- 4.6 ID / 屏幕切换 / 时间线等辅助接口
- 4.6.1 对象 ID(在启用 `LV_USE_OBJ_ID` 时)
- 4.6.2 屏幕加载与时间线动画快捷接口
- 5 设计优势与缺点(含案例)
- 5.1 设计优势
- 5.2 潜在缺点与注意事项
- 5.3 案例:异步删除避免事件重入问题
- 5.4 案例:使用 flags 与 scroll API 构建自定义滚动容器
- 6 与其它框架的对比与改进思路
- 6.1 与 AWTK 的对比
- 6.2 与 Qt 的对比
- 6.3 与 Android / HTML/CSS 的对比
- 6.4 可能的改进方向
- 6.5 与 LVGL 8.4 的对象系统纵向对比与演进原因
- 6.5.1 结构体与模块拆分层面的变化
- 6.5.2 Flags/状态与属性系统的协同增强
- 6.5.3 API 行为与生命周期管理上的变化与原因推测
- 7 小结
- 8 附录
- A 参考文档(外部)
- B 相关资源(CSDN 系列)
文档版本: 1.0
更新日期: 2025年12月
适用对象: 希望深入理解 LVGL9.4 核心对象系统(lv_obj*)的工程师(框架设计 / 控件开发 / 上层框架作者)
1 概述
1.1 文档目的
本篇聚焦library/lvgl/src/core/lv_obj*系列文件,从框架开发者视角解析 LVGL9.4 核心对象系统的设计思路与在整体架构中的定位,说明:
lv_obj_t在整个控件体系中的角色;- 对象树(tree)、几何/布局(pos)、滚动(scroll)、属性与标志位(flags/states)如何协同;
- 对象创建/销毁、有效性检查、ID/名称等生命周期相关机制。
在此基础上,通过接口分类与 API 速查表、典型案例和与其它 GUI 框架的横向对比,帮助读者建立一套“看得懂内核、敢改敢扩展、知道边界与坑点在哪里”的对象系统心智模型。
1.2 代码版本与范围
- 仓库路径:
https://github.com/lvgl/lvgl.git - 版本:v9.4.0
- 主要关注源码文件:
library/lvgl/src/core/lv_obj.hlibrary/lvgl/src/core/lv_obj.clibrary/lvgl/src/core/lv_obj_private.hlibrary/lvgl/src/core/lv_obj_tree.h/lv_obj_tree.clibrary/lvgl/src/core/lv_obj_pos.h/lv_obj_pos.clibrary/lvgl/src/core/lv_obj_scroll.h/lv_obj_scroll.c
- 密切相关模块(单独成文):
lv_obj_class*:对象类系统与继承机制(见09-core-obj_class文档);lv_group*:焦点组与键盘/编码器导航(见09-core-group文档);lv_global*:全局上下文与 display/输入设备管理(见09-core-global文档)。
2 设计意图与总体定位
2.1lv_obj_t在 LVGL 中扮演的角色
在 LVGL 中,所有可见控件与容器的共同基础都是lv_obj_t:
- 每一个按钮、标签、进度条、列表项,底层都是一个
lv_obj_t实例加上对应的类描述(lv_obj_class_t)和样式集合; - 所有 UI 元素共同挂在一棵“对象树”上,根结点是各个 screen(屏幕顶层对象);
- 事件派发、绘制、布局、滚动、焦点状态等行为,最终都围绕
lv_obj_t进行。
可以把lv_obj_t看成 LVGL 的“C 版 DOM 节点”或“C 版 QWidget 基类”:
- 自身只保存最通用的字段(类指针、坐标、状态、标志位、样式链表等);
- 通过类系统(
lv_obj_class*)挂接具体控件行为; - 通过对象树(
lv_obj_tree*)组织父子/兄弟关系; - 通过 pos/scroll/style 子模块实现几何、滚动与外观控制。
2.2 对象内部结构的关键字段
在lv_obj_private.h中可以看到lv_obj_t的核心结构(略去宏与条件编译):
- 基础关联信息:
const lv_obj_class_t * class_p:对象所属类描述,决定构造/析构/事件/默认行为;lv_obj_t * parent:父对象指针(screen 的父指针为NULL);lv_obj_spec_attr_t * spec_attr:指向“稀疏扩展属性”的结构体。
- 样式与用户数据:
lv_obj_style_t * styles:与对象关联的样式链表;void * user_data:用户自定义数据指针;- 可选
void * id:在启用LV_USE_OBJ_ID时用于对象 ID。
- 几何与状态:
lv_area_t coords:对象在父容器内的坐标与尺寸;lv_obj_flag_t flags:控制对象行为的 flag 位(可点击/可滚动/可见性等);uint16_t state:对象逻辑状态(LV_STATE_PRESSED、LV_STATE_FOCUSED等,可与样式联动)。
- 布局/内部标志位:
layout_inv/scr_layout_inv:布局是否需要重新计算;h_layout/w_layout:是否受布局管理;is_deleting:对象是否处于删除过程中,用于避免递归删除死循环。
lv_obj_spec_attr_t则承载低频使用但占空间较大的字段,例如:
- 子对象数组
lv_obj_t ** children及child_cnt; lv_group_t * group_p:所属焦点组;- 事件列表
lv_event_list_t event_list; - 滚动偏移
lv_point_t scroll、扩展点击/绘制区域、滚动条模式、scroll snap 配置等; - 可选的名字
const char * name,用于调试和查找。
这种“主体结构 + 可选扩展”的设计,使得:
- 普通对象在未添加子节点/事件/滚动配置时不分配
spec_attr,节省内存; - 一旦需要这些能力,再按需分配扩展结构,兼顾了嵌入式场景的内存敏感性。
2.3 对象生命周期与对象树
对象的典型生命周期流程如下:
- 创建:
- 应用层调用
lv_obj_create(parent)或某个 widget 的create函数; - 内部通过
lv_obj_class_create_obj(MY_CLASS, parent)分配对象,设置class_p和parent; - 调用
lv_obj_class_init_obj(obj):- 标记布局为脏、暂时禁用样式刷新;
- 应用当前主题样式;
- 递归调用类/基类构造函数;
- 恢复样式刷新并重新计算自适应尺寸;
- 若开启默认 group 且类描述
group_def为 TRUE,则自动加入默认 group; - 向父对象发送
LV_EVENT_CHILD_CHANGED/LV_EVENT_CHILD_CREATED等事件。
- 应用层调用
- 运行期:
- 通过 pos/scroll/style 等 API 修改几何与外观;
- 通过 flags/state 控制交互行为;
- 通过事件回调响应输入、动画、屏幕切换等。
- 销毁:
- 调用
lv_obj_delete(obj)或相关变体:- 递归删除所有子对象;
- 从 group 中移除、移除动画与事件回调;
- 触发
LV_EVENT_DELETE,确保用户有机会清理外部引用; - 更新父对象的滚动条与布局状态。
- 调用
lv_obj_tree.*就是围绕这棵对象树,提供删除、清空、调整父子关系、查询屏幕/显示器、遍历子节点等操作的核心模块。
3 使用方式与典型场景
3.1 创建对象与构建对象树
一个最小的对象创建示例:
lv_obj_t*scr=lv_screen_active();/* 获取当前活动 screen */lv_obj_t*cont=lv_obj_create(scr);/* 创建一个容器对象 */lv_obj_t*child1=lv_obj_create(cont);/* 子对象 1 */lv_obj_t*child2=lv_obj_create(cont);/* 子对象 2 */lv_obj_set_size(cont,200,100);lv_obj_center(cont);在这个例子中:
scr作为根节点,其parent == NULL,挂在 display 上;cont/child1/child2共享同一棵对象树,通过lv_obj_get_parent/lv_obj_get_child等接口可以互相导航;- 对容器进行移动/滚动/隐藏,会整体影响其子节点。
更复杂的 UI(如多页面、嵌套布局、滚动列表等),本质都是在这棵对象树上增加更多节点与布局策略。
3.2 标志位(flags)与状态(states)的常见用法
对象的交互行为由flags与state共同决定:
flags更偏向能力配置:是否可点击、是否可滚动、是否参与布局等;state更偏向当前状态:是否被按下、是否获得焦点、是否禁用等。
一个典型用法示例:
lv_obj_t*btn=lv_button_create(scr);/* 设置为可点击,并在点击时获得焦点 */lv_obj_add_flag(btn,LV_OBJ_FLAG_CLICKABLE|LV_OBJ_FLAG_CLICK_FOCUSABLE);/* 设置为不可用(禁用态),样式层可以根据 DISABLED 状态绘制灰掉效果 */lv_obj_add_state(btn,LV_STATE_DISABLED);通过合理组合flags与state,可以在无需修改控件实现的前提下,精细控制对象的交互语义与视觉表现。例如:
- 使用
LV_OBJ_FLAG_SCROLLABLE+lv_obj_scroll_*构建自定义滚动容器; - 使用
LV_STATE_FOCUSED配合lv_group实现键盘/编码器焦点高亮; - 使用
LV_OBJ_FLAG_HIDDEN实现逻辑隐藏(对象仍在树中,但不参与绘制与事件)。
3.3 几何、布局与滚动的典型组合场景
lv_obj_pos.*与lv_obj_scroll.*提供了大量面向对象几何与滚动的高层 API,常见场景包括:
- 手动布局:
- 使用
lv_obj_set_pos/lv_obj_set_size精确摆放子对象; - 使用
lv_obj_align/lv_obj_align_to/lv_obj_center实现相对位置对齐; - 使用
lv_obj_set_content_width/height与LV_SIZE_CONTENT让容器根据子对象自动伸缩。
- 使用
- 基于布局引擎:
- 为容器设置布局(如 Flex/Grid),再通过
lv_obj_update_layout触发布局重算; - 子对象只需要设置尺寸与布局相关样式,即可自动排布。
- 为容器设置布局(如 Flex/Grid),再通过
- 滚动容器:
- 为对象添加
LV_OBJ_FLAG_SCROLLABLE并配置lv_obj_set_scroll_dir、lv_obj_set_scrollbar_mode; - 使用
lv_obj_scroll_by/lv_obj_scroll_to或lv_obj_scroll_to_view(_recursive)控制滚动行为; - 使用 scroll snap 与滚动事件,实现类似列表吸附、分页滚动等体验。
- 为对象添加
这些能力共同构成 LVGL 对象系统在“几何 + 滚动 + 布局”维度上的基础设施。
4 接口分类与 API 速查表
说明:本节按功能维度整理
lv_obj*相关核心接口,具体签名与更多细节以当前版本源码为准。
4.1 创建与基础信息相关接口
| 功能 | 接口 | 说明 |
|---|---|---|
| 创建基础对象(矩形) | lv_obj_create(parent) | 基于lv_obj_class创建一个最基础的对象 |
| 获取对象类 | lv_obj_get_class(obj) | 返回对象当前的类描述指针 |
| 检查类型是否匹配 | lv_obj_check_type(obj, class_p) | 是否恰好是指定类 |
| 检查是否属于某类(含祖先) | lv_obj_has_class(obj, class_p) | 沿类继承链判断是否属于某类 |
| 检查对象是否有效 | lv_obj_is_valid(obj) | 检查对象是否尚未被删除/破坏 |
| 在删除时自动置空引用 | lv_obj_null_on_delete(&obj_ptr) | 对象删除时自动将外部指针清为NULL |
| 获取/设置用户数据 | lv_obj_get_user_data(obj)/lv_obj_set_user_data(obj, ptr) | 挂载业务侧自定义数据 |
小结:这些接口为“对象是谁、类是什么、还活着吗、我能挂点私货吗”提供了统一入口,是所有上层封装的基础。
4.2 标志位(flags)与状态(states)管理
4.2.1 Flags 操作接口
| 功能 | 接口 | 说明 |
|---|---|---|
| 添加 flag | lv_obj_add_flag(obj, f) | 将一个或多个lv_obj_flag_t置位 |
| 移除 flag | lv_obj_remove_flag(obj, f) | 清除指定 flag |
| 统一设置 flag | lv_obj_set_flag(obj, f, bool v) | true添加,false移除 |
| 检查全部 flag 是否存在 | lv_obj_has_flag(obj, f) | 所有给定 flag 都已置位则返回 true |
| 检查任意 flag 是否存在 | lv_obj_has_flag_any(obj, f) | 至少一个 flag 置位则返回 true |
常见 flag 示例(来自lv_obj_flag_t):
- 可见性与交互:
LV_OBJ_FLAG_HIDDEN、LV_OBJ_FLAG_CLICKABLE、LV_OBJ_FLAG_CHECKABLE; - 滚动相关:
LV_OBJ_FLAG_SCROLLABLE、LV_OBJ_FLAG_SCROLL_ELASTIC、LV_OBJ_FLAG_SCROLL_MOMENTUM等; - 事件/状态传播:
LV_OBJ_FLAG_EVENT_BUBBLE、LV_OBJ_FLAG_STATE_TRICKLE等; - 自定义扩展位:
LV_OBJ_FLAG_WIDGET_1/2、LV_OBJ_FLAG_USER_1..4供控件/业务使用。
4.2.2 状态(states)操作接口
| 功能 | 接口 | 说明 |
|---|---|---|
| 添加状态 | lv_obj_add_state(obj, state) | 增加一个或多个lv_state_t |
| 移除状态 | lv_obj_remove_state(obj, state) | 移除给定状态 |
| 统一设置状态 | lv_obj_set_state(obj, state, bool v) | 添加或移除 |
| 获取当前状态位掩码 | lv_obj_get_state(obj) | 返回 OR 后的状态集合 |
| 检查是否处于某状态 | lv_obj_has_state(obj, state) | 如果包含给定状态则返回 true |
典型状态包括:LV_STATE_PRESSED、LV_STATE_FOCUSED、LV_STATE_CHECKED、LV_STATE_DISABLED等,这些状态会与样式系统中的“状态选择器”联动,决定使用哪套视觉样式。
4.3 对象树与层级操作(lv_obj_tree.*)
4.3.1 删除与清理
| 功能 | 接口 | 说明 |
|---|---|---|
| 删除对象及其所有子对象 | lv_obj_delete(obj) | 递归删除,并发送LV_EVENT_DELETE |
| 删除所有子对象 | lv_obj_clean(obj) | 保留自己,清空所有子节点 |
| 延迟删除对象 | lv_obj_delete_delayed(obj, delay_ms) | 适合动画结束后删除 |
| 动画回调删除对象 | lv_obj_delete_anim_completed_cb(a) | 可直接作为动画完成回调 |
| 异步删除对象 | lv_obj_delete_async(obj) | 通过lv_async_call在安全时机删除 |
工程实践中,在事件回调(尤其是
LV_EVENT_DELETE)里删除父子对象时,应优先考虑异步删除,以避免重入和悬挂指针问题。
4.3.2 父子关系与顺序调整
| 功能 | 接口 | 说明 |
|---|---|---|
| 重新设置父对象 | lv_obj_set_parent(obj, parent) | 保持相对坐标不变,调整到新父对象下 |
| 交换两个对象的位置 | lv_obj_swap(obj1, obj2) | 同一父下交换顺序,可用于排序 |
| 按索引移动对象 | lv_obj_move_to_index(obj, index) | 将对象移动到父节点子列表中的指定位置 |
4.3.3 查询屏幕、display、父子/兄弟与子节点计数
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取所属 screen | lv_obj_get_screen(obj) | 一直向上追溯,返回根 screen |
| 获取所属 display | lv_obj_get_display(obj) | 返回对象所在显示器 |
| 获取父对象 | lv_obj_get_parent(obj) | 返回直接父节点 |
| 按索引获取子对象 | lv_obj_get_child(obj, idx) | 支持从头/尾计数(负数索引) |
| 按类型获取子对象 | lv_obj_get_child_by_type(obj, idx, class_p) | 仅在指定类的子对象中索引 |
| 获取兄弟对象 | lv_obj_get_sibling(obj, idx) | 相对当前对象的前/后兄弟 |
| 按类型获取兄弟对象 | lv_obj_get_sibling_by_type(obj, idx, class_p) | 只在指定类的兄弟中寻找 |
| 获取子节点数量 | lv_obj_get_child_count(obj) | 返回直接子节点个数 |
| 获取指定类型子节点数量 | lv_obj_get_child_count_by_type(obj, class_p) | 只统计指定类的子节点数量 |
4.3.4 名称与查找(在启用LV_USE_OBJ_NAME时)
| 功能 | 接口 | 说明 |
|---|---|---|
| 设置对象名称(动态分配) | lv_obj_set_name(obj, name) | 名称字符串由 LVGL 分配与释放 |
| 设置静态名称 | lv_obj_set_name_static(obj, name) | 仅保存指针,不复制内容 |
| 获取原始名称 | lv_obj_get_name(obj) | 返回设置时的名称指针 |
| 获取解析后的唯一名称 | lv_obj_get_name_resolved(obj, buf, size) | 支持xxx_#自动附加编号 |
| 按名称查找子对象 | lv_obj_find_child_by_name(parent, name)等 | 支持广度优先查找 |
名称机制主要用于调试与 UI 编辑器,对业务逻辑不建议强依赖“字符串查找”。
4.4 几何与布局相关接口(lv_obj_pos.*)
4.4.1 位置与尺寸
| 功能 | 接口 | 说明 |
|---|---|---|
| 设置位置 | lv_obj_set_pos(obj, x, y) | 相对 alignment 的偏移 |
| 设置 X/Y 坐标 | lv_obj_set_x(obj, x)/lv_obj_set_y(obj, y) | 独立设置 |
| 设置整体尺寸 | lv_obj_set_size(obj, w, h) | 支持像素、LV_SIZE_CONTENT、百分比 |
| 重算尺寸 | lv_obj_refr_size(obj) | 根据内容/布局重新计算大小 |
| 设置宽/高 | lv_obj_set_width/height(obj, v) | 支持内容自适应与百分比 |
| 设置内容宽/高 | lv_obj_set_content_width/height(obj, v) | 扣除 padding 与边框后的内容区域尺寸 |
4.4.2 布局与对齐
| 功能 | 接口 | 说明 |
|---|---|---|
| 设置布局 | lv_obj_set_layout(obj, layout) | 绑定 Flex/Grid 等布局描述 |
| 判断是否由布局定位 | lv_obj_is_layout_positioned(obj) | 用于区分“手动坐标 vs 布局管理” |
| 标记布局为脏 | lv_obj_mark_layout_as_dirty(obj) | 触发布局重算 |
| 立即更新布局 | lv_obj_update_layout(obj) | 强制进行布局计算 |
| 设置对齐方式 | lv_obj_set_align(obj, align) | 如LV_ALIGN_CENTER等 |
| 对齐并设置偏移 | lv_obj_align(obj, align, x_ofs, y_ofs) | 一次性设置对齐与偏移量 |
| 相对另一对象对齐 | lv_obj_align_to(obj, base, align, x_ofs, y_ofs) | 用于 tooltip/弹窗等场景 |
| 居中对齐 | lv_obj_center(obj) | 等价于对父对象居中 |
4.4.3 坐标与变换
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取对象坐标区域 | lv_obj_get_coords(obj, &area) | 包含位置与尺寸 |
| 设置变换矩阵 | lv_obj_set_transform(obj, matrix) | 需启用LV_DRAW_TRANSFORM_USE_MATRIX |
| 重置变换矩阵 | lv_obj_reset_transform(obj) | 变回单位矩阵 |
这些接口与布局/样式系统一起决定了对象在屏幕上的最终几何表现。
4.5 滚动与滚动条相关接口(lv_obj_scroll.*)
4.5.1 滚动配置
| 功能 | 接口 | 说明 |
|---|---|---|
| 设置滚动条模式 | lv_obj_set_scrollbar_mode(obj, mode) | LV_SCROLLBAR_MODE_OFF/ON/AUTO/ACTIVE |
| 设置滚动方向 | lv_obj_set_scroll_dir(obj, dir) | 按lv_dir_t位标志组合 |
| 设置水平 snap 模式 | lv_obj_set_scroll_snap_x(obj, align) | 基于lv_scroll_snap_t枚举 |
| 设置垂直 snap 模式 | lv_obj_set_scroll_snap_y(obj, align) | 同上 |
4.5.2 滚动状态查询
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取滚动条模式 | lv_obj_get_scrollbar_mode(obj) | 返回lv_scrollbar_mode_t |
| 获取可滚动方向 | lv_obj_get_scroll_dir(obj) | 返回当前 dir 掩码 |
| 获取 scroll snap 配置 | lv_obj_get_scroll_snap_x/y(obj) | 返回lv_scroll_snap_t |
| 获取当前 scroll 偏移 | lv_obj_get_scroll_x/y(obj) | 当前滚动偏移量 |
| 查询可滚动范围 | lv_obj_get_scroll_top/bottom/left/right(obj) | 剩余可滚动距离 |
| 获取滚动结束位置 | lv_obj_get_scroll_end(obj, &pt) | 若有滚动动画则为动画结束位置 |
4.5.3 滚动控制与可见性
| 功能 | 接口 | 说明 |
|---|---|---|
| 按增量滚动 | lv_obj_scroll_by(obj, dx, dy, anim) | 不限制越界 |
| 按增量滚动(受边界限制) | lv_obj_scroll_by_bounded(obj, dx, dy, anim) | 只在内容区域内滚动 |
| 滚动到指定偏移 | lv_obj_scroll_to(obj, x, y, anim) | 设置目标 scroll 坐标 |
| 单轴滚动 | lv_obj_scroll_to_x/y(obj, v, anim) | 仅 X 或 Y 方向 |
| 滚动到子对象可见 | lv_obj_scroll_to_view(obj, anim) | 滚动父容器直到子对象可见 |
| 递归滚动到可见 | lv_obj_scroll_to_view_recursive(obj, anim) | 适用于嵌套滚动容器 |
| 判断是否正在滚动 | lv_obj_is_scrolling(obj) | 包含滚动动画中的情形 |
4.6 ID / 屏幕切换 / 时间线等辅助接口
4.6.1 对象 ID(在启用LV_USE_OBJ_ID时)
| 功能 | 接口 | 说明 |
|---|---|---|
| 设置对象 ID | lv_obj_set_id(obj, id) | 保存一个指针作为 ID |
| 获取对象 ID | lv_obj_get_id(obj) | 返回此前设置的 ID 指针 |
| 通过 ID 查找子对象 | lv_obj_find_by_id(obj, id) | 递归查找匹配 ID 的子对象 |
| 自动分配 ID | lv_obj_assign_id(class_p, obj) | 由内建或外部实现自动生成 ID |
| 释放 ID 资源 | lv_obj_free_id(obj) | 删除对象前清理由 ID 占用的资源 |
| 比较两个 ID | lv_obj_id_compare(id1, id2) | 返回 0 表示相等 |
| 将 ID 格式化为字符串 | lv_obj_stringify_id(obj, buf, len) | 便于调试输出 |
4.6.2 屏幕加载与时间线动画快捷接口
| 功能 | 接口 | 说明 |
|---|---|---|
| 绑定事件触发屏幕加载 | lv_obj_add_screen_load_event(obj, trigger, screen, anim, duration, delay) | 在指定事件发生时加载目标 screen |
| 绑定事件触发屏幕创建 | lv_obj_add_screen_create_event(obj, trigger, create_cb, anim, duration, delay) | 动态创建 screen 并在卸载时自动删除 |
| 绑定事件触发时间线动画 | lv_obj_add_play_timeline_event(obj, trigger, at, delay, reverse) | 用于按钮触发复杂动画 |
这些接口虽然“长得不像基础对象 API”,但正是借助对象系统的事件与生命周期机制实现了高层行为组合,在工程实践中非常实用。
5 设计优势与缺点(含案例)
5.1 设计优势
统一的对象抽象,降低框架心智复杂度
- 所有控件(按钮、列表、图表等)最终都落在同一种
lv_obj_t对象模型上; - 上层开发者只需要熟悉一套“对象 + 类 + 树 + 样式”组合,即可理解绝大多数行为。
- 所有控件(按钮、列表、图表等)最终都落在同一种
对象树 + flags/states 提供清晰的语义层次
- 对象树负责“谁包含谁、谁在谁之上”,flags/states 负责“能不能点、有没有焦点、是不是禁用”;
- 这种拆分让布局/绘制/输入路由各司其职,便于后续重构与优化。
稀疏扩展属性(
spec_attr)兼顾功能与内存- 高频访问字段(类、父指针、坐标、flags/states)常驻在
lv_obj_t; - 低频字段(子列表、滚动配置、事件列表、名称等)按需分配在
lv_obj_spec_attr_t; - 在嵌入式场景下,既保证了对象的功能完整,又防止空对象过度占用内存。
- 高频访问字段(类、父指针、坐标、flags/states)常驻在
与类系统、group、global 等子系统天然协同
class_p将对象与类系统直接绑定,支持继承与多态;group_p与lv_group集成,实现键盘/编码器焦点导航;lv_obj_get_screen/lv_obj_get_display与lv_global配合,串联 display 与全局状态。
5.2 潜在缺点与注意事项
对象系统本身较为复杂,新手心智成本高
- 初学者同时接触对象树、类系统、样式、事件、布局与滚动,容易迷失在调用链里;
- 调试一个“点不动的按钮”时,可能需要同时检查 flags、states、group、事件回调、样式状态等多个维度。
对象树操作涉及动态内存与重排,性能敏感场景需谨慎
- 例如
lv_obj_set_parent会对父节点的 children 数组进行realloc与元素移动,频繁动态改变树结构可能带来碎片与性能压力; - 在大批量对象创建/销毁场景(如虚拟化列表、频繁刷新的图表)中,需要格外注意树操作的复杂度。
- 例如
删除与生命周期管理容易踩坑
- 在
LV_EVENT_DELETE回调中删除父对象或兄弟对象,如果直接调用lv_obj_delete,可能触发递归删除重入; - 若外部仍持有指向被删对象的裸指针,稍不注意就会造成悬挂引用,需要结合
lv_obj_null_on_delete或业务侧包装。
- 在
5.3 案例:异步删除避免事件重入问题
在复杂界面中,经常会在某个对象的事件回调里删除自己或父节点,例如:
staticvoidclose_btn_event_cb(lv_event_t*e){lv_obj_t*btn=lv_event_get_target(e);lv_obj_t*dialog=lv_obj_get_parent(btn);/* 直接删除 dialog 可能在事件派发栈中造成重入 */lv_obj_delete(dialog);}如果其它控件在LV_EVENT_DELETE中又访问 dialog 的某些子对象,就有可能出现“正在删除中的对象被再次操作”的风险。
更稳妥的做法是使用异步删除:
staticvoidclose_btn_event_cb(lv_event_t*e){lv_obj_t*btn=lv_event_get_target(e);lv_obj_t*dialog=lv_obj_get_parent(btn);lv_obj_delete_async(dialog);/* 推迟到安全时机统一删除 */}借助对象系统提供的异步删除能力,可以大大减少删除路径上的重入与悬挂指针问题。
5.4 案例:使用 flags 与 scroll API 构建自定义滚动容器
假设你需要做一个自定义的滚动面板:
- 只允许垂直滚动;
- 滚动时显示滚动条,停止后自动隐藏;
- 子项希望在滚动停止时自动“对齐到顶部”。
可以这样组合对象系统接口:
lv_obj_t*panel=lv_obj_create(parent);lv_obj_set_size(panel,200,150);/* 打开垂直滚动能力 */lv_obj_add_flag(panel,LV_OBJ_FLAG_SCROLLABLE);lv_obj_set_scroll_dir(panel,LV_DIR_VER);/* 滚动条只在滚动时显示 */lv_obj_set_scrollbar_mode(panel,LV_SCROLLBAR_MODE_ACTIVE);/* 停止时按顶部对齐子项 */lv_obj_set_scroll_snap_y(panel,LV_SCROLL_SNAP_START);这展示了对象系统在“几何 + flags + 滚动”维度的协作方式:
底层仍然只是一个lv_obj_t,但通过配置 flag 与滚动/布局 API,就能组合出相当丰富的 UI 容器行为。
6 与其它框架的对比与改进思路
6.1 与 AWTK 的对比
- AWTK 同样在 C 语言环境下实现了
widget_t+ 虚函数表的控件体系:- 通过
widget_t承载控件树、事件与绘制; - 使用 type/name 做 RTTI 与调试。
- 通过
- LVGL 的
lv_obj_t与之类似,但更强调:- 把 flags/states、滚动配置、group 引用等行为元信息挂在统一对象结构上;
- 借助
lv_obj_property与属性系统,为上层编辑器与脚本绑定提供统一入口。
启发:在基于 LVGL 搭建上层框架(如 XSLVGL4.0)时,可以借鉴 AWTK 在 widget/type 层面的命名与 RTTI 经验,在lv_obj_t之上增加更统一的“运行时类型/属性描述层”,方便工具和脚本消费。
6.2 与 Qt 的对比
- Qt 使用 C++ 的
QWidget/QQuickItem类体系与元对象系统:- 类继承 + 虚函数实现多态;
- 属性系统、信号/槽等通过 MOC 生成的元对象描述器实现。
- LVGL 的
lv_obj_t+lv_obj_class_t可以视为“C 语言版简化 QWidget + 元信息”:- 用结构体 + 函数指针模拟类与虚函数;
- 用属性 ID + getter/setter 表模拟属性系统。
启发:若项目需要在 LVGL 上提供“脚本友好 UI 层”(如 Lua/JS 配置 UI),可以在lv_obj_t与lv_obj_class_t之上构建一层轻量元对象系统:
- 为每类对象分配稳定的 type id 与字符串类型名;
- 暴露属性表给脚本侧;
- 把事件回调包装成“信号/槽”式接口。
6.3 与 Android / HTML/CSS 的对比
- Android:
View/ViewGroup树与 LayoutParams 管理几何与布局;- XML 布局 + 反射/属性系统实现声明式 UI;
- ScrollView/RecyclerView 等控件内建滚动与回收逻辑。
- HTML/CSS:
- DOM 树 + CSS 盒模型 + 事件模型;
- 滚动、可见性、交互状态(:hover/:focus/:active)通过属性和 CSS 控制。
对比来看,LVGL 的对象系统处在更底层的位置:
- 提供“对象树 + 几何 + 滚动 + 状态/标志”等核心能力,但不直接提供 RecyclerView 那种高层组件;
- 通过 flags/states 与属性系统,为在其之上构建“布局描述语言 / 配置驱动 UI / 虚拟化列表”等高级能力提供基础。
6.4 可能的改进方向
从性能角度:
- 对 children 数组的
realloc与移动可以在大规模动态界面中成为瓶颈,未来可以考虑更高效的数据结构(例如分段数组或轻量链表 + 索引缓存); - 对于频繁刷新/销毁的大量对象场景,可以探索对象池(object pool)与批量删除优化。
- 对 children 数组的
从代码结构角度:
- 将
lv_obj_t拆分为更清晰的子结构(如 geometry、interaction、scroll、naming 等),在内部保持组合,而对外保持统一 API; - 为对象树与布局/滚动提供更明显的边界与模块化封装,便于不同项目按需裁剪。
- 将
从 API 设计角度:
- 针对常见组合场景(如滚动面板、卡片列表)提供更高层的 helper API 或“预设控件模板”;
- 为调试与可视化工具暴露统一的对象树 dump 接口(结构化 JSON、调试协议等),方便在 IDE 或外部工具中查看对象树与状态。
6.5 与 LVGL 8.4 的对象系统纵向对比与演进原因
6.5.1 结构体与模块拆分层面的变化
对比 8.4 与 9.4 的源码(主要是lv_obj.h与lv_obj_private.h),可以看到对象系统在结构体组织和模块划分上有几处明显演进:
- 内部结构体的拆分与私有化增强:
- 8.4 中
_lv_obj_t与_lv_obj_spec_attr_t多数定义直接出现在公开头文件中,虽然也有一定的“spec_attr 稀疏扩展”设计,但对上层来说内部字段比较“透明”; - 9.4 将完整对象结构收敛到
lv_obj_private.h,在公开头文件中只暴露必要的 typedef 与 API,使lv_obj_t更加被视为“框架内核的私有实现细节”,为后续重构预留空间。
- 8.4 中
- spec_attr 承载的信息更完整、更统一:
- 8.4 中
_lv_obj_spec_attr_t已包含 children/scroll/ext_draw_size 等字段,但名称、事件列表等能力还相对分散; - 9.4 中
lv_obj_spec_attr_t明确接管了子节点数组、组引用、事件列表、名称(含静态/动态标记)、滚动配置、扩展绘制区域等“低频但占空间”的信息,使得:lv_obj_t本体更专注于类指针、父指针、坐标、flags/states 等高频字段;- spec_attr 成为“所有附加能力的统一容器”,方便后续属性系统与调试工具直接对接。
- 8.4 中
- ID / user_data / style cache 等可选能力的处理:
- 8.4 中
user_data受编译选项控制,ID 能力相对简单; - 9.4 引入
LV_USE_OBJ_ID等配置,将 ID 管理(包括通过 ID 查找对象)与对象结构统一起来,同时在私有结构中增加样式缓存位(如style_main_prop_is_set),有助于减少样式计算的重复开销。
- 8.4 中
总体来说,9.4 在保持“主体 + 稀疏扩展”设计思路的同时,进一步强化了对象内部结构的模块化与封装性,让上层更多通过 API 与属性系统交互,而不是依赖结构体字段细节。
6.5.2 Flags/状态与属性系统的协同增强
在 flags、state 与属性系统的结合上,9.4 相比 8.4 有几个关键改进点:
- flags 定义与属性 ID 的严格映射:
- 8.4 中虽然也有一套完整的
lv_obj_flag_t,但基本只在 C API 层使用; - 9.4 在
lv_obj.h中通过LV_PROPERTY_ID(OBJ, FLAG_xxx, ...)这类宏,为每一个 flag/状态分配稳定的属性 ID,使得:- UI 编辑器或脚本可以统一通过“属性 ID + getter/setter”的形式操作 flags/states;
- 对象系统内部可以更容易地做反射式处理(例如 dump 全部 flags/states、做序列化等)。
- 8.4 中虽然也有一套完整的
- 状态体系与属性表的深度绑定:
- 8.4 中
lv_state_t主要作为样式系统的状态选择器; - 9.4 将状态位同样纳入属性系统之中,配合对象属性表,使“当前是否 pressed/disabled/focused”等状态可以以统一方式被外部工具读取与修改。
- 8.4 中
从工程效果上看,9.4 明显是为“属性驱动 / 工具驱动”的使用场景做了更充分的准备,而 8.4 更偏向“仅供 C API 直接调用”的传统用法。
6.5.3 API 行为与生命周期管理上的变化与原因推测
在公开 API 层面,8.4 与 9.4 都保持了lv_obj_create、flags/states 操作、对象树增删查改等核心接口,但行为细节与整体思路上也有一些可见的演进:
- 删除与异步安全性更加重视:
- 8.4 时期已经有
lv_obj_del_async等接口,但在文档与实践中使用并不普遍; - 9.4 在对象私有结构中增加类似
is_deleting标志位,并在文档与示例中更积极地推荐使用异步删除/延迟删除,明确是为了减少事件回调中的删除重入与悬挂指针问题。
- 8.4 时期已经有
- 与类系统、属性系统的耦合更加紧密:
- 8.4 中对象更多是“承载控件树与样式的基础类型”;
- 9.4 中
lv_obj_t明确成为“类系统(lv_obj_class_t)+ 属性系统(lv_obj_property)+ group/滚动/布局”等多子系统的汇聚点,很多行为(默认 group、主题继承、属性暴露)都需要类描述与对象结构协同完成。
- 演进原因的综合推测:
- 随着 LVGL 使用场景从“纯 C 工程师手写 UI”扩展到“配合 UI 编辑器 / 配置中心 / 脚本”的场景,仅靠 8.4 版本的对象设计已经不足以支撑更丰富的工具链;
- 9.4 在不破坏现有 API 习惯的前提下,通过:
- 强化对象内部结构的封装与模块化;
- 将 flags/states/几何/滚动等纳入统一属性系统;
- 在删除与生命周期上加强安全性;
把lv_obj*从“一个 UI 控件基类”升级为“面向工具和上层框架的稳定内核抽象”。
换句话说:8.4 的对象系统已经足够支撑手写 C UI,9.4 则是在这个基础上,系统性地补上了“工具友好、属性驱动、安全删除与扩展性”的短板,为在 LVGL 之上构建更高级别的 UI 框架和生态(脚本、编辑器、配置平台)打下基础。
7 小结
lv_obj*系列代码构成了 LVGL9.4 对象系统的核心:
- 通过统一的
lv_obj_t结构组织所有 UI 元素,以对象树的形式串联父子关系,并由 flags/states/属性系统承载交互语义; - 通过 pos/scroll/tree 等子模块提供几何、滚动、布局与生命周期管理能力,为控件实现与上层框架提供坚实基础;
- 在工程实践中,理解
lv_obj_t的结构、生命周期与常用 API,是读懂 LVGL 内核、实现复杂控件与上层 UI 框架的前置条件。
8 附录
A 参考文档(外部)
- LVGL 官方文档:对象系统
- LVGL 官方文档:滚动与滚动条
- LVGL 官方文档:样式与状态
- LVGL GitHub 仓库
B 相关资源(CSDN 系列)
- 【奶茶Beta专项】【LVGL9.4源码分析】01-目录结构
- 【奶茶Beta专项】【LVGL9.4源码分析】02-编译框架-Cmake详解
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-display
- 【奶茶Beta专项】【LVGL9.4源码分析】04-OS抽象层
- 【奶茶Beta专项】【LVGL9.4源码分析】05-标准库