news 2026/7/15 21:23:52

LVGL学习(四)- Objects

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL学习(四)- Objects

一、简介

前面介绍的东西都比较简单,这篇文章介绍点有深度的东西。

二、什么是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() 等)操作对象,不能直接读写字段

关键字段说明

字段

类型

作用

对应 API

class_p

const lv_obj_class_t *

类信息,决定这是哪种控件。Label 和 Button 的区别就在这里

内部使用

parent

lv_obj_t *

父对象,形成对象树

lv_obj_get_parent()

spec_attr

lv_obj_spec_attr_t *

特殊属性(按需分配),包含事件回调、子对象链表、滚动信息等

lv_obj_allocate_spec_attr()

styles

lv_obj_style_t *

样式数组

lv_obj_set_style_*()

coords

lv_area_t

坐标区域 (x1,y1,x2,y2),由它推算出位置和尺寸

lv_obj_get_pos()/lv_obj_get_width()

flags

lv_obj_flag_t

标志位(可点击、可滚动、隐藏等)

lv_obj_add_flag()/lv_obj_clear_flag()

state

uint16_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); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 21:18:48

2026市面上目前靠谱的扫码点餐小程序服务商哪家专业好用?实测推荐

在餐饮行业竞争日益激烈的当下,扫码点餐小程序成为了众多商家提升服务效率、降低运营成本的利器。然而,市场上扫码点餐小程序服务商众多,让人眼花缭乱,究竟哪家才专业好用呢?今天,咱们就来实测推荐一番。用…

作者头像 李华
网站建设 2026/7/15 21:17:45

JDK 12 新特性详解

JDK 12 新特性详解 JDK 12 于 2019年3月19日 正式发布,是一个非 LTS(Non-LTS)版本。该版本引入了多个重要的语言特性预览(Switch 表达式、Pattern Matching),以及多项 JVM 和 GC 优化(G1 增强、ZGC 改进、Shenandoah GC 正式纳入)。JDK 12 为后续 LTS 版本(JDK 17)的…

作者头像 李华
网站建设 2026/7/15 21:15:35

Redis典型应用 - 分布式锁,引入过期时间,Lua脚本原子解锁操作,看门狗机制实现过期时间动态续约,Redlock算法

目录 什么是分布式锁? 多节点对共享资源的访问 分布式锁的基础实现 实现步骤引入过期时间引入校验机制引入 Lua 对 Lua 的介绍 引入看门狗 (watch dog) 两个时间概念具体例子注意点(对锁的续约时间理解) 引入 Redlock 算法 解决方案 什么是分布式锁? 多节点对…

作者头像 李华
网站建设 2026/7/15 21:15:05

新材料制造工业数据中台解决方案

某新材料制造型企业拥有多个生产厂房及自动化生产设备,涵盖配料、成型、切割、中涂、分色、包装等工艺流程。为全面推进数字化转型,企业计划构建数字化车间管理机系统,重点在于全面采集各工艺及产线设备数据,并实现对设备监控、故…

作者头像 李华
网站建设 2026/7/15 21:13:47

构建高性能存储网络:NVMe-oF、SPDK与RDMA的融合实践

1. 为什么需要高性能存储网络?现代数据中心对存储性能的要求越来越高。传统存储架构在应对AI训练、高频交易、实时分析等场景时,往往面临延迟高、吞吐低的瓶颈。想象一下,当你在玩在线游戏时,如果每次加载地图都要等待几秒钟&…

作者头像 李华
网站建设 2026/7/15 21:13:03

2026免费数字人平台怎么选:基于功能、成本、体验的实测选型框架,新手、老板IP和商用团队分别看什么

2026免费数字人平台怎么选:基于功能、成本、体验的实测选型框架,新手、老板IP和商用团队分别看什么 如果你正在搜“免费数字人平台怎么选”,很容易被两类内容带偏。 一类是单纯的排行榜:把平台从第一名排到第十名; 另一…

作者头像 李华