1-2-3_IMX6ULL运行LVGL
烧写为学习lvgl定制的系统
将拨码开关拨至USB烧写模式:
烧录完成后将拨码开关设为EMMC启动模式:
登录用户名为:root。不需要密码,输入 root 之后回车即可
配置交叉编译工具链
将资料目录中的 “03_开发软件\imx6ull_toolchain\toolchain.zip” 解压
得到 “arm-buildroot-linux-musleabihf_sdk-buildroot.tar.gz”,将其上传到虚拟机系统中
在虚拟机中执行命令解压:tar -xvf arm-buildroot-linux-musleabihf_sdk-buildroot.tar.gz
配置工具链路径。
编译源码
将源码复制到虚拟机
检查工具链配置
进入目录执行 make clean && make
编译过程的文件放在obj目录下,bin文件放在bin目录下
将bin目录下的bin文件上传到开发板
修改开发板中bin文件的权限: chmod +x 100ask_lvgl_demo
运行: ./100ask_lvgl_demo
2-1-1_对象(lv_obj_t)
LVGL采用面向对象的编程思想(OOP),她的基本构造块(类)是对象(实例),也就是我们所说的部件(Widgets)就是一个个部件,比如button、label、image等等。
我们先来看看怎么用面向对象的编程思想表示一只狗:有五官、四肢、颜色、会吠叫、会呼吸、性格等。
那么我就可以这样定义一只狗(类):五官四肢正常、白色、叫声响亮、呼吸稳重、性格乖巧。
上面那样说得太抽象,我要具体表示一只边境牧羊犬(类的实例)呢?
五官四肢正常、毛色黑白两色、叫声响亮、呼吸稳重、性格温和忠诚。
如果要追本溯源的话,那边境牧羊犬的父类是动物,因为狗就是动物。如果还要再说得抽象一点:边境牧羊犬是生物。(生物->动物->狗->边境牧羊犬)
那如果我要表示另一个品种的狗呢?比如泰迪,这两个品种的差异很大,首先体型、外观就差别很大。但是他们都是狗,他们的父类是狗,他们拥有狗最基本的属性。
通过面向对象的思想,我们可以很简便、形象地表达出一只狗(实例)。
其实lvgl也是使用这样的编程思想:lv_obj_t (类)定义了部件的抽象特点,其定义包含了数据的形式以及对数据的操作。部件(子类)比原本的类(称为父类或基类)要更加具体化,子类会继承父类的属性和行为。
1. 基类结构体:lv_obj_t
- 概念类比:
就像生物学中所有狗都继承自犬科动物的基础基因,lv_obj_t
是LVGL中所有控件的"基因模板"。它定义了所有控件共有的基础属性,如位置、大小、父子关系等。 - 代码实现:
LVGL通过结构体嵌套实现继承。例如,按钮控件lv_btn_t
的结构体内部会包含lv_obj_t
作为基类,并扩展特有属性(如按下状态、标签等):typedef struct { lv_obj_t base; // 继承自基类 char* label; // 按钮特有属性:标签文本 uint8_t state; // 按钮状态(如按下/释放) } lv_btn_t;
2. 动态实例化:创建不同风格的控件
- 核心机制:
通过动态内存分配创建控件实例,并基于父类lv_obj_t
扩展功能。例如,lv_btn_create()
函数内部会调用lv_obj_create()
创建基类对象,再添加按钮特有的属性和方法。 - 个性化样式:
每个控件实例可以通过样式系统独立定制外观。例如,创建两个按钮并分别设置红色和蓝色背景:// 创建基类按钮 lv_obj_t *btn1 = lv_btn_create(lv_scr_act()); lv_obj_t *btn2 = lv_btn_create(lv_scr_act()); // 独立设置样式 lv_obj_set_style_bg_color(btn1, lv_palette_main(LV_PALETTE_RED), 0); // 红色按钮[8](@ref) lv_obj_set_style_bg_color(btn2, lv_palette_main(LV_PALETTE_BLUE), 0); // 蓝色按钮
3. 多态与事件响应
- 事件回调:
所有控件通过函数指针实现多态行为。例如,按钮点击事件和滑块拖动事件会触发不同的回调函数:// 按钮点击事件 lv_obj_add_event_cb(btn1, btn_click_handler, LV_EVENT_CLICKED, NULL); // 滑块拖动事件 lv_obj_add_event_cb(slider, slider_drag_handler, LV_EVENT_VALUE_CHANGED, NULL);
- 方法重写:
控件可以覆盖基类方法。例如,图表控件lv_chart_t
会重写draw
方法以实现数据可视化。
4. 父子关系与层级管理
- 容器化设计:
父对象(如屏幕、容器控件)作为子对象的"容器"。当父对象移动或隐藏时,所有子对象会同步变化。例如,删除一个父容器会递归删除其所有子对象。 - 图层系统:
LVGL通过act_scr
(活动屏幕层)、top_layer
(顶层,如弹窗)、sys_layer
(系统层,如光标)实现层级管理,确保界面元素按优先级渲染。
5. 为何选择C语言实现?
- 嵌入式友好:
C语言更轻量,避免C++的虚函数表、异常处理等开销,适合资源有限的嵌入式设备。 - 灵活可控:
通过手动管理内存和继承关系,开发者能精准控制性能(如避免动态类型检查的开销)。
总结:从基因到多样性
LVGL的lv_obj_t
如同生物界的"DNA模板",所有控件通过继承和扩展实现功能分化。这种设计既保证了代码复用(如统一的位置计算、事件分发),又允许开发者通过样式和事件系统赋予控件千变万化的外观和交互逻辑。正如同一犬种因毛色、体型不同而呈现多样性,LVGL控件也因样式和事件响应的差异展现出丰富的UI效果。
2-2-1_基础对象(lv_obj)
屏幕是没有父类的基础对象
屏幕对象的创建过程
LVGL的三层屏幕
1. 活动屏幕层 lv_scr_act()
- 作用:这是用户当前直接操作的主界面,相当于舞台的主表演区。所有基础控件(按钮、标签等)默认都创建在这个层级上。
- 特点:
- 同一时间只能有一个活动屏幕,切换屏幕时会自动更新。
- 屏幕之间可以通过动画切换(如滑动、淡入淡出),增强交互体验。
- 例如:显示登录界面、主菜单界面等。
- 代码示例:
lv_obj_t *screen = lv_scr_act(); // 获取当前活动屏幕 lv_scr_load(new_screen); // 切换到新屏幕
2. 顶层 lv_layer_top()
- 作用:相当于舞台上的悬浮展示台,用于覆盖在活动屏幕之上的全局元素,如弹窗、菜单栏或通知。
- 特点:
- 跨屏幕可见,所有屏幕共享同一个顶层。
- 若启用点击属性(
LV_OBJ_FLAG_CLICKABLE
),点击顶层会阻止下层交互(类似模态弹窗)。 - 例如:弹出警告框、全局设置菜单。
- 代码示例:
lv_obj_t *popup = lv_btn_create(lv_layer_top()); // 在顶层创建弹窗 lv_obj_add_flag(popup, LV_OBJ_FLAG_CLICKABLE); // 启用点击拦截
3. 系统层 lv_layer_sys()
- 作用:相当于舞台的灯光与音效控制台,用于显示必须始终可见的系统级元素,如鼠标光标、输入法键盘或状态栏。
- 特点:
- 位于层级最顶端,覆盖所有其他层级。
- 内容不受屏幕切换影响,始终保持可见。
- 例如:触摸屏的光标、实时更新的网络状态图标。
- 代码示例:
lv_obj_t *cursor = lv_img_create(lv_layer_sys()); // 在系统层创建光标 lv_img_set_src(cursor, &mouse_icon); // 设置光标图标
层级关系与交互规则
- 叠加顺序:
系统层(最前)→ 顶层 → 活动屏幕层(最后)。 - 交互优先级:
点击事件会优先传递给最顶层的对象,若顶层未处理才会传递到下层。 - 内存管理:
删除活动屏幕时,其子对象会被自动清理;但顶层和系统层的对象需手动管理。
实际应用场景
- 场景1 - 弹窗提示:
在用户点击按钮时,通过lv_layer_top()
创建确认弹窗,确保用户必须处理弹窗后才能继续操作下层界面。 - 场景2 - 动态光标:
在触摸屏设备中,通过lv_layer_sys()
显示实时跟随触点的光标,避免被其他界面元素遮挡。 - 场景3 - 多屏切换:
使用lv_scr_load_anim()
实现不同活动屏幕之间的平滑过渡动画(如滑动切换)。
通过合理使用这三个层级,可以构建出复杂且交互流畅的用户界面,同时避免元素之间的视觉和逻辑冲突。
2-2-2_基础对象的大小(size)
2-2-3_基础对象的位置(Position)
LVGL屏幕的原点
屏幕(部件区域)的表示
位置(Position)
一、对齐的本质:空间定位规则
对齐就像贴纸在画板上的摆放规则:
- 父容器是画板(如屏幕、窗口、容器控件)
- 子控件是贴纸(如按钮、标签)
- 对齐方式决定了贴纸贴在画板的哪个位置(如左上角、正中间)
二、对齐的三大关键要素
-
对齐类型(lv_align_t)
定义了控件基准点与父容器参考点的对应关系,共有9种基础模式:对齐类型 作用描述 类比场景 LV_ALIGN_TOP_LEFT
贴纸左上角对齐画板左上角 将按钮固定在窗口左上角 LV_ALIGN_CENTER
贴纸中心对齐画板中心 居中显示登录对话框 LV_ALIGN_BOTTOM_RIGHT
贴纸右下角对齐画板右下角 右下角的"确定"按钮 -
偏移量(x_offset, y_offset)
在对齐基础上进行微调,类似贴纸贴好后用手指轻轻推移:// 将按钮对齐到父容器中心,并向上偏移20像素 lv_obj_align(btn, LV_ALIGN_CENTER, 0, -20); // [1,6](@ref)
- 正偏移:向右(x)或向下(y)移动
- 负偏移:向左(x)或向上(y)移动
-
参考对象
可以灵活选择对齐基准:- 默认参考父容器:
lv_obj_align(btn, LV_ALIGN_TOP_LEFT, 10, 10)
(按钮左上角距离父容器左上角向右、向下各10像素) - 参考其他控件:
lv_obj_align_to(label, image, LV_ALIGN_OUT_BOTTOM_MID, 0, 10)
(标签显示在图片下方中间,间隔10像素)
- 默认参考父容器:
三、两类典型对齐场景
-
父子容器对齐
适用于控件直接嵌套在父容器内:// 创建父容器(类似一个白色底板) lv_obj_t *parent = lv_obj_create(lv_scr_act()); lv_obj_set_size(parent, 200, 200); // 子按钮对齐到父容器右下角,并留出10像素边距 lv_obj_align(child_btn, LV_ALIGN_BOTTOM_RIGHT, -10, -10); // [1](@ref)
-
兄弟控件对齐
实现控件之间的相对位置(无需父子关系):// 图片和文字组成水平排列的图标 lv_obj_align_to(label, icon, LV_ALIGN_OUT_RIGHT_MID, 10, 0); // [6](@ref)
→ 图标右侧间隔10像素显示文字
四、高级技巧与避坑指南
-
动态适配
当父容器大小变化(如屏幕旋转)时,需调用lv_obj_align()
重新对齐。 -
百分比偏移
使用LV_PCT()
实现响应式布局(需启用LV_USE_PERCENTAGE
):// 向右偏移父容器宽度的20%,向下偏移高度的30% lv_obj_align(obj, LV_ALIGN_TOP_LEFT, LV_PCT(20), LV_PCT(30)); // [1](@ref)
-
坐标系差异
LVGL采用LCD坐标系(原点在左上角,向下为Y轴正方向),与传统笛卡尔坐标系相反,设置偏移时需特别注意方向。 -
层级覆盖规则
后创建的对象默认覆盖在先创建对象之上,可通过lv_obj_move_foreground()
调整层级。
五、为什么需要对齐?
- 提升开发效率:避免手动计算坐标,一键实现精准布局
- 保持界面一致性:适配不同分辨率屏幕时自动调整
- 支持复杂交互:弹窗、菜单等动态元素能快速定位
对齐类型(LV_ALIGN_...)
获取位置(Get position)
2-2-4_基础对象的盒子模型(border-box)
LVGL对象的盒子模型
一、盒子模型的构成(快递盒类比)
想象每个LVGL控件都是一个快递包裹盒子,其结构分解如下:
1. 边界框(Bounding Box)
- 作用:整个盒子的外包装尺寸(宽×高),决定了控件在父容器中占用的最大空间。
- 代码示例:
lv_obj_set_size(btn, 100, 50); // 设置按钮边界框为100x50像素
- 特点:无论内部如何设计(如边框、填充),边界框的尺寸始终不变,类似快递盒的外箱体积。
2. 边框(Border)
- 作用:包裹在内容区域外的装饰性线条,类似快递盒的纸板厚度和颜色。
- 代码设置:
lv_style_set_border_width(&style, 3); // 边框宽度3像素 lv_style_set_border_color(&style, lv_palette_main(LV_PALETTE_BLUE)); // 蓝色边框
- 特性:边框属于边界框内部,会挤压内容区域的空间(如100px宽的控件,边框占5px则内容仅剩90px)。
3. 填充(Padding)
- 作用:内容区域与边框之间的缓冲空间,类似快递盒内部防震泡沫的厚度。
- 代码设置:
lv_style_set_pad_all(&style, 10); // 四周填充10像素
- 影响:填充越大,内容区域越小。例如按钮的标签文字会被限制在更小的范围内显示。
4. 内容(Content)
- 作用:控件的核心显示区域,如按钮上的文字、图片的实际显示位置。
- 自动调整:当内容超出区域时可能被裁剪,需通过
LV_SIZE_CONTENT
属性自适应:lv_obj_set_width(label, LV_SIZE_CONTENT); // 标签宽度根据文本自动调整
5. 轮廓(Outline)
- 作用:在边框外围绘制的装饰线,类似用荧光笔在快递盒外画的标记,不占用实际空间。
- 代码设置:
lv_style_set_outline_width(&style, 2); // 轮廓线宽度2像素 lv_style_set_outline_color(&style, lv_color_hex(0xFF0000)); // 红色轮廓
- 用途:常用于高亮选中状态(如焦点框),不会影响其他控件的布局。
二、与CSS盒模型的差异
关键区别:无外边距(Margin)
- LVGL设计:不提供外边距属性,控件间距需手动通过坐标对齐或父容器的填充实现。
- 替代方案:
- 轮廓(Outline):用于非占位性装饰。
- 布局器(Flex/Grid):通过父容器的排列规则自动管理子控件间距。
尺寸计算规则
LVGL遵循border-box模型,即:
总宽度 = 设定宽度(包含边框+填充+内容)
总高度 = 设定高度(包含边框+填充+内容)
这与浏览器默认的content-box
模型不同(后者总尺寸=内容+边框+填充)。
三、实际应用场景
1. 按钮设计
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 40); // 边界框尺寸
// 设置样式
lv_style_set_border_width(&btn_style, 2); // 边框2px
lv_style_set_pad_hor(&btn_style, 15); // 水平填充15px
lv_style_set_outline_width(&btn_style, 1); // 焦点轮廓1px
效果:按钮文字左右留白15px,点击时显示红色轮廓但不影响布局。
2. 弹窗布局
lv_obj_t *popup = lv_obj_create(lv_layer_top());
lv_obj_set_size(popup, 200, 150);
// 通过填充实现内边距
lv_style_set_pad_all(&popup_style, 20); // 弹窗内容四周留白20px
效果:弹窗内容区域自动缩小为160x110px,周围留出呼吸空间。
四、调试与优化建议
- 边界可视化:开发阶段可为父容器添加轮廓线,直观查看布局关系。
- 响应式设计:使用
LV_PCT()
百分比单位或LV_SIZE_CONTENT
实现自适应。 - 层级叠加:通过
lv_layer_top()
放置弹窗,避免轮廓与其他控件冲突。
一、盒子模型四层结构解析
-
内容(Content)
- 生活比喻:盒子里装的物品,如书籍、玩具,对应网页中的文字、图片或LVGL中的按钮、标签等控件。
- 特性:
- CSS中内容区域通过
width
/height
设定,默认会撑大盒子 - LVGL中内容溢出时不改变盒子尺寸,通过滚动条查看(如
lv_page
组件)
- CSS中内容区域通过
-
内边距(Padding)
- 生活比喻:盒内填充的泡沫或气泡膜,用于保护内容物。
- 作用:
- 控制内容与边框的间距,如按钮文字与边缘的距离
- LVGL中通过
lv_style_set_pad_all()
设置四边统一填充
-
边框(Border)
- 生活比喻:快递盒的纸板厚度与颜色,如加厚瓦楞纸或彩色封箱胶带。
- 特性:
- CSS支持单边差异化边框(如左厚右薄)
- LVGL边框四边统一,通过
lv_style_set_border_width()
设置厚度
-
外边距(Margin)
- 生活比喻:货架上盒子间的空隙,防止碰撞。
- 特殊差异:
- CSS通过
margin
控制元素间距 - LVGL无外边距,改用
outline
轮廓线(如高亮选中状态)
- CSS通过
二、CSS与LVGL盒子模型对比
特性 | CSS标准盒模型 | LVGL盒模型 |
---|---|---|
尺寸计算 | content-box | border-box (含边框+填充) |
溢出处理 | 撑大父容器 | 保持尺寸,启用滚动条 |
外边距实现 | 通过margin | 轮廓线outline 替代 |
边框灵活性 | 支持四边差异化 | 四边统一 |
三、实际应用场景
-
网页设计
- 使用
margin: auto;
实现居中布局 - 通过
box-sizing: border-box
统一尺寸计算
- 使用
-
LVGUI开发
- 按钮美化:设置填充+边框模拟立体感
lv_style_set_pad_hor(&btn_style, 20); // 水平填充20像素 lv_style_set_border_width(&btn_style, 3); // 3像素边框[12](@ref)
- 焦点提示:用红色轮廓线替代选中态外边距
lv_style_set_outline_color(&focus_style, lv_palette_main(LV_PALETTE_RED))[16](@ref)
- 按钮美化:设置填充+边框模拟立体感
总结
理解盒子模型如同掌握包装艺术:
- 内容是核心,填充保障安全,边框定义形态,间距(或轮廓)营造秩序。
- 开发者需注意:LVGL的
border-box
特性会压缩内容区域,而CSS默认的content-box
更易导致布局失控。合理运用这些属性,能让界面既美观又高效。
2-2-5_基础对象的样式(styles)
样式(Styles)
1. 样式是什么?——「服装搭配」
- 本质:样式是一组视觉属性的集合(如颜色、字体、边框),相当于给控件“穿衣服”。
- 代码示例:
static lv_style_t style_btn; // 定义一个“服装套装” lv_style_set_bg_color(&style_btn, lv_color_hex(0x3498db)); // 设置蓝色背景 lv_style_set_radius(&style_btn, 10); // 圆角10像素
2. 样式的核心规则——「穿衣法则」
(1) 级联叠加:穿多层衣服
- 规则:一个控件可叠加多个样式,后添加的样式覆盖先前的,就像先穿白衬衫再套红马甲
- 。
-
lv_obj_add_style(btn, &style_base, 0); // 基础样式:灰色按钮 lv_obj_add_style(btn, &style_highlight, 0); // 覆盖为红色按钮
-
(2) 继承与优先级——「家族遗传」
- 继承:子控件默认继承父控件的文本颜色等属性,但可自定义覆盖。
- 优先级:
本地样式 > 后添加的样式 > 先添加的样式 > 父级继承
相当于:个人定制西装 > 外搭风衣 > 内搭衬衫 > 家族传统服饰。
3. 状态与部件——「场景化换装」
(1) 状态(State):不同场合换装
- 常见状态:默认(正常)、按下(点击)、禁用(不可用)等。
- 应用场景:按钮按下时变色、禁用时变灰。
// 默认状态:蓝色背景 lv_style_set_bg_color(&style_btn, LV_STATE_DEFAULT, lv_color_hex(0x3498db)); // 按下状态:深蓝色背景 lv_style_set_bg_color(&style_btn, LV_STATE_PRESSED, lv_color_hex(0x2c3e50));
(2) 部件(Part):精细化设计
- 控件拆解:一个控件由多个部件组成,如按钮=背景(MAIN)+ 文字(LABEL)。
- 独立设计:可单独设置按钮背景圆角,同时调整文字字体。
lv_obj_add_style(btn, &style_bg, LV_PART_MAIN); // 背景样式 lv_obj_add_style(btn, &style_text, LV_PART_LABEL); // 文字样式
4. 高级功能——「动态特效」
(1) 过渡动画
- 效果:状态切换时属性渐变(如按钮按下时颜色平滑过渡)。
lv_style_set_transition_time(&style_btn, 300); // 过渡时间300ms lv_style_set_transition_prop_1(&style_btn, LV_STYLE_BG_COLOR); // 背景色渐变
(2) 本地样式——「临时补丁」
- 特点:仅对单个控件生效,优先级最高。
lv_obj_set_style_bg_color(btn, lv_color_hex(0xff0000), LV_PART_MAIN); // 临时改红色
5. 主题(Theme)——「整体装修方案」
- 作用:一套预定义的全局样式集合,一键切换UI风格(如白天/黑夜模式)。
- 代码示例:
lv_theme_t my_theme; // 定义主题 my_theme.style_btn = &style_btn_custom; // 自定义按钮样式 lv_theme_set_current(&my_theme); // 应用主题
总结:样式设计的四大原则
- 组合复用:通过级联减少重复代码(如全局基础样式+局部覆盖样式)。
- 场景适配:用状态和部件实现交互反馈(如按钮按下效果)。
- 动态体验:过渡动画提升视觉流畅度。
- 统一管理:主题机制保证界面风格一致性。
通过灵活运用这些规则,开发者可以像“服装设计师”一样,快速打造专业级嵌入式UI界面。
初始化样式
设置样式属性
当我们初始化好一个样式之后就可以设置它的样式属性了,接口函数是这样的格式:
lv_style_set_<property_name>(&style, <value>);
示例:
lv_style_set_bg_color(&style_obj, lv_color_hex(0x000000)); // 设置背景色
lv_style_set_bg_opa(&style_obj, LV_OPA_50); // 设置背景透明度
lv_style_set_....
添加(应用)样式到对象
当我们初始化并且设置好一个样式之后就可以将它添加到对象上面了,接口函数只有一个:
lv_obj_add_style(obj, &style, <selector>)
参数 “obj” 就是要添加到的对象,“style” 是指向样式变量的指针,<selector> 是应添加样式的部分和状态的 OR-ed 值 (不能是互斥,否则就是清除标志,没法合并)。示例:
lv_obj_add_style(obj, &style_obj, 0); // 默认(常用)
lv_obj_add_style(obj, &style_obj, LV_STATE_PRESSED); // 在对象被按下时应用样式
获取样式属性
我们可以获取属性的最终值(考虑级联、继承、本地样式和转换),接口函数是这样的格式:
lv_obj_get_style_<property_name>(obj, <part>);
函数使用对象的当前状态,如果没有更好的候选对象,则返回默认值。 例如:
lv_color_t color = lv_obj_get_style_bg_color(obj, LV_PART_MAIN);
删除样式
删除对象的所有样式:
lv_obj_remove_style_all(obj);
删除对象的特定样式:
lv_obj_remove_style(obj, &style_obj, selector);
只有当 selector 与 lv_obj_add_style 中使用的 selector 匹配时,此函数才会删除 style
如果 style 是空,那么会根据给出的 selector 检查并删除所有匹配的样式
如果 selector 是 LV_STATE_ANY 或 LV_PART_ANY 就会删除具有任何状态或部分的样式。下面这个效果和lv_obj_remove_style_all 的效果是一样的:
lv_obj_remove_style(obj, NULL, LV_STATE_ANY | LV_PART_ANY );
查看样式属性
所有的可用的样式属性我们可以在文档或者代码中获取得到。
文档位置:
英文原版:Style properties — LVGL documentation
中文翻译:Style properties — 百问网LVGL中文教程文档 文档
代码位置:
普通样式:lvgl/src/misc/lv_style_gen.h
本地样式:lvgl/src/core/lv_obj_style_gen.h
文档位置和代码位置可能在后续的版本更新中会发生变化,这里的方法只是提供参考,不需要死记硬背函数接口名。
背景部分的属性
背景属性和我们前面学习的盒子模型关系很大,背景属性主要有一下这些:
背景(Background)
边界(Border)
轮廓(Outline)
阴影(Shadow)
填充(Padding)
宽度和高度变换
X和Y变换
样式的状态和部分
部分(Part)
对象可以有 部分(parts) ,它们也可以有自己的样式。LVGL 中存在以下预定义部分:
LV_PART_MAIN 类似矩形的背景
LV_PART_SCROLLBAR 滚动条
LV_PART_INDICATOR 指标,例如用于滑块、条、开关或复选框的勾选框
LV_PART_KNOB 像手柄一样可以抓取调整值
LV_PART_SELECTED 表示当前选择的选项或部分
LV_PART_ITEMS 如果小部件具有多个相似元素(例如表格单元格)
LV_PART_TICKS 刻度上的刻度,例如对于图表或仪表
LV_PART_CURSOR 标记一个特定的地方,例如文本区域或图表的光标
LV_PART_CUSTOM_FIRST 可以从这里添加自定义部件。
这些可能会随着lvgl的更新而不断增加,同学们可以阅读最新版本的文档获取最新资料。
本地样式
一、定义与初始化
1. 普通样式
- 特点:全局共享,需手动初始化并存储为静态/全局变量。
- 代码流程:
static lv_style_t style_btn; // 定义全局样式变量 lv_style_init(&style_btn); // 初始化样式 lv_style_set_bg_color(&style_btn, lv_color_hex(0x000000)); // 设置背景色 lv_style_set_border_width(&style_btn, 2); // 设置边框宽度
2. 本地样式
- 特点:无需单独定义样式变量,直接通过对象接口设置。
- 代码示例:
lv_obj_t *btn = lv_btn_create(lv_scr_act()); // 创建按钮对象 lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF5722), LV_PART_MAIN); // 直接设置本地背景色 lv_obj_set_style_radius(btn, 8, LV_PART_MAIN); // 直接设置圆角
二、应用方式
1. 普通样式
- 多对象复用:通过
lv_obj_add_style
函数绑定到对象,支持多个对象共享同一套样式-
-
lv_obj_t *obj1 = lv_obj_create(lv_scr_act()); lv_obj_add_style(obj1, &style_btn, LV_STATE_DEFAULT); // 应用样式到对象1 lv_obj_t *obj2 = lv_obj_create(lv_scr_act()); lv_obj_add_style(obj2, &style_btn, LV_STATE_DEFAULT); // 同一样式应用到对象2
-
-
2. 本地样式
- 单对象独占:通过
lv_obj_set_style_xxx
函数直接修改对象属性,仅影响当前对象。lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED); // 仅修改当前按钮的按下状态背景色
三、内存管理
1. 普通样式
- 手动管理:需显式初始化(
lv_style_init
)和重置(lv_style_reset
),适用于长期复用的全局风格。
2. 本地样式
- 自动释放:随对象生命周期自动分配和释放内存,无需手动销毁。
四、优先级与覆盖
1. 普通样式
- 低优先级:若多个普通样式作用于同一对象,后添加的样式会覆盖先前的同名属性。
2. 本地样式
- 最高优先级:直接覆盖普通样式的同名属性,即使普通样式后添加。
五、状态与部件的处理
1. 普通样式
- 需显式指定状态/部件:通过
lv_obj_add_style
的第三个参数指定生效场景-
-
lv_obj_add_style(btn, &style_pressed, LV_STATE_PRESSED); // 仅在按下状态生效
-
-
2. 本地样式
- 内嵌状态/部件参数:在设置函数中直接通过参数指定。
lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED | LV_PART_MAIN);
六、版本差异(LVGL V7 vs V8)
- 函数接口简化:在V8中,普通样式的设置函数形参更简洁(如去除状态参数),而本地样式接口保持不变。
// V7写法 lv_style_set_bg_color(&style, LV_STATE_DEFAULT, LV_COLOR_WHITE); // V8简化 lv_style_set_bg_color(&style, LV_COLOR_WHITE);
总结
对比维度 | 普通样式 | 本地样式 |
---|---|---|
作用范围 | 全局共享 | 单对象私有 |
内存管理 | 手动初始化和释放 | 自动绑定对象生命周期 |
优先级 | 低(被本地样式覆盖) | 最高 |
适用场景 | 统一主题、高频复用属性 | 临时调整、个性化定制 |
代码复杂度 | 需定义变量和初始化 | 直接通过对象接口设置 |
推荐策略:
- 使用普通样式统一管理全局设计规范(如主题色、字体)。
- 使用本地样式快速微调特定控件的特殊状态(如按钮悬停高亮)。
样式继承
在LVGL中,样式继承机制可以类比为现实生活中的“家族传统”——子元素会继承父元素的某些特性,但也可以通过个性化设置打破传统。以下是这一机制的通俗解析:
1. 继承的核心逻辑:未设置则找父级
- 触发条件:当子控件(如按钮内的标签)未显式设置某个样式属性(如文本颜色)时,才会触发继承机制。
- 搜索过程:LVGL会沿着控件树向上逐级查找父控件,直到找到第一个定义了该属性的父对象为止。这类似于“家族族谱寻根”,找不到就一直往上追溯。
- 示例:
若按钮未设置文本颜色,则继承父容器(如窗口)的文本颜色;若父容器也未设置,则继续向上查找,直到找到定义该属性的祖先或使用默认值。
2. 继承与状态的联动:动态匹配父级状态
- 状态敏感:继承时,父控件的当前状态会影响子控件继承的值。例如:
- 父按钮处于按下状态时,子标签会继承父按钮按下状态的文本颜色;
- 父按钮处于默认状态时,子标签则继承默认状态的文本颜色。
- 应用场景:
当父控件的交互状态变化(如点击、聚焦)时,子控件的继承属性会动态跟随,实现视觉一致性。
3. 可继承属性的范围:文本类为主
- 典型可继承属性:
- 文本颜色(
text_color
) - 字体(
text_font
) - 字号(
text_letter_space
) - 对齐方式(
text_align
)
- 文本颜色(
- 不可继承属性:
背景色、边框尺寸等与布局和容器直接相关的属性通常不可继承。
4. 优先级规则:个性化高于继承
继承的优先级低于所有显式设置的样式,具体规则如下:
子控件的本地样式 > 子控件的普通样式 > 父控件的样式 > 默认值
- 示例:
若父按钮设置文本为红色,而子标签显式设置为蓝色,则子标签显示蓝色,而非继承父级的红色。
实际场景举例
假设设计一个带标签的按钮:
// 父按钮设置按下状态文本为白色
lv_obj_set_style_text_color(parent_btn, lv_color_white(), LV_STATE_PRESSED);
// 子标签未设置文本颜色
lv_obj_t *child_label = lv_label_create(parent_btn);
lv_label_set_text(child_label, "Click Me");
- 交互效果:
当父按钮被按下时,子标签文本自动变为白色;松开后恢复默认颜色(继承父按钮的默认状态颜色)。
总结
LVGL的样式继承机制通过以下特点实现高效设计:
- 懒加载:仅在需要时触发继承,节省资源;
- 状态感知:动态匹配父级状态,增强交互一致性;
- 可控性:通过显式设置属性即可中断继承链,灵活定制界面。
开发者可通过这种机制快速搭建风格统一但可局部定制的界面,如同家族中既有共同传统,又允许成员个性表达。
过渡特效
默认情况下,当一个对象改变状态(例如它被按下)时,新状态的新属性会立即设置。但是,通过转换,可以在状态更改时播放动画。 例如,按下按钮时,其背景颜色可以在 300 毫秒内动画显示为按下的颜色。
demo体验:
https://docs.lvgl.io/8.1/overview/style.html#transition
http://lvgl.100ask.net/8.1/overview/style.html#transition
这部分我们在后面课程再展开讨论
样式主题
主题是风格的集合。如果存在活动主题,LVGL将其应用于每个创建的部件(对象)。 这将为UI提供一个默认外观,然后可以通过添加更多样式对其进行修改。
demo体验:
https://docs.lvgl.io/8.1/overview/style.html#extending-the-current-theme
http://lvgl.100ask.net/8.1/overview/style.html#extending-the-current-theme
这部分后面的课程再展开讨论。
2-2-6_基础对象的事件(events)
一、事件回调的本质:拆包裹
当用户点击按钮、拖动滑块等操作发生时,LVGL会生成一个“事件包裹”(lv_event_t
),并交给开发者预设的回调函数处理。你需要拆开包裹,根据里面的信息做出反应。
二、包裹里的四大核心工具
1. 事件类型(lv_event_get_code
)
- 作用:识别包裹类型(是“点击快递”还是“滑动快递”)。
- 代码示例:
lv_event_code_t code = lv_event_get_code(e); // 拆包看事件类型 if(code == LV_EVENT_CLICKED) { // 如果是点击事件 printf("按钮被点击了!"); }
2. 触发对象(lv_event_get_target
)
- 作用:知道是谁发出的包裹(如点击的是哪个按钮)。
- 代码示例:
lv_obj_t *target = lv_event_get_target(e); // 获取触发对象 lv_obj_set_style_bg_color(target, lv_color_hex(0xFF0000), 0); // 把按钮变红
3. 原始触发对象(lv_event_get_target_obj
)
- 作用:事件冒泡时追溯源头(如子控件的事件传递到父容器)。
- 比喻:快递包裹可能经过多个站点转发,但原始寄件人不变。
- 代码示例:
lv_obj_t *origin = lv_event_get_target_obj(e); // 获取最初触发对象
4. 用户数据(lv_event_get_user_data
)
- 作用:包裹里附带的“小纸条”(自定义参数)。
- 使用场景:区分多个按钮共用同一个回调函数时的逻辑。
- 代码示例:
// 绑定回调时传递数据(如按钮ID) lv_obj_add_event_cb(btn, my_event_handler, LV_EVENT_CLICKED, (void*)1001); // 回调函数中读取数据 int button_id = (int)lv_event_get_user_data(e); // 获取ID=1001
三、事件冒泡机制:包裹层层上报
- 流程:
当子控件(如按钮)的事件未被处理时,会像冒泡一样逐级传递给父容器(类似DOM事件冒泡)。 - 控制方法:
在回调函数中调用lv_event_stop_bubbling(e)
可阻止冒泡。
四、实际应用场景
场景1:动态修改按钮颜色
void event_handler(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
lv_obj_t *btn = lv_event_get_target(e);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x00FF00), 0); // 点击后变绿
}
}
场景2:多按钮共享回调
// 创建两个按钮,传递不同ID
lv_obj_add_event_cb(btn1, event_handler, LV_EVENT_CLICKED, (void*)1);
lv_obj_add_event_cb(btn2, event_handler, LV_EVENT_CLICKED, (void*)2);
// 回调函数内区分按钮
int id = (int)lv_event_get_user_data(e);
printf("按钮%d被点击", id);
五、总结
- 事件包裹 (
lv_event_t
) 是交互逻辑的“信息宝箱”,通过拆解它,开发者可以精准响应操作。 - 核心口诀:
- 看类型(
get_code
) - 找对象(
get_target
) - 读数据(
get_user_data
) - 控冒泡(
stop_bubbling
)
- 看类型(
掌握这些工具,你就能像处理快递一样高效管理LVGL的交互事件!
一、什么是事件冒泡?
1. 生活比喻
想象你在学校闯了祸:
- 子对象:你(搞事的学生)
- 父对象:班主任 → 年级主任 → 校长
- 冒泡规则:
你搞事后,班主任先处理;若班主任不管,年级主任接手;若年级主任也不管,最终校长出面。
2. LVGL实现
- 启用冒泡:给对象添加
LV_OBJ_FLAG_EVENT_BUBBLE
标志lv_obj_add_flag(button, LV_OBJ_FLAG_EVENT_BUBBLE); // 开启按钮的冒泡
- 传递流程:
子对象事件 → 父对象 → 祖父对象 → ... → 根对象
每个层级的父对象都能收到事件通知,直至有人处理或冒泡终止。
二、为什么要用冒泡?
1. 场景一:统一管理子对象事件
- 问题:一个父容器内有100个按钮,每个按钮单独处理点击事件,代码重复。
- 冒泡解决方案:
// 父容器统一处理所有子按钮的点击事件 void parent_event_handler(lv_event_t *e) { lv_obj_t *target = lv_event_get_target(e); // 获取具体哪个按钮被点击 lv_obj_t *current = lv_event_get_current_target(e); // 父容器自身 printf("按钮%p被点击,由父容器%p处理", target, current); } // 父容器启用冒泡处理 lv_obj_add_event_cb(parent, parent_event_handler, LV_EVENT_CLICKED, NULL);
2. 场景二:动态生成对象
- 问题:运行时动态创建的控件(如聊天消息),无法预先绑定事件。
- 冒泡优势:
父容器无需知道具体子对象,统一通过冒泡捕获事件,实现动态响应。
三、核心函数解析
1. 获取触发源:lv_event_get_target(e)
- 作用:找到最初触发事件的具体对象(如被点击的按钮)。
- 代码示例:
lv_obj_t *clicked_btn = lv_event_get_target(e); // 直接定位到“闯祸的学生”
2. 获取当前处理者:lv_event_get_current_target(e)
- 作用:知道当前处理事件的父对象层级(如正在处理的班主任)。
- 代码示例:
lv_obj_t *current_handler = lv_event_get_current_target(e); // 当前处理的父容器
四、冒泡机制如何工作?
代码流程示例
// 创建祖父-父-子三层结构
lv_obj_t *grandparent = lv_obj_create(lv_scr_act());
lv_obj_t *parent = lv_obj_create(grandparent);
lv_obj_t *child = lv_btn_create(parent);
// 全部启用冒泡
lv_obj_add_flag(child, LV_OBJ_FLAG_EVENT_BUBBLE);
lv_obj_add_flag(parent, LV_OBJ_FLAG_EVENT_BUBBLE);
lv_obj_add_flag(grandparent, LV_OBJ_FLAG_EVENT_BUBBLE);
// 点击child按钮时,事件传递顺序:
child → parent → grandparent
五、什么时候需要阻止冒泡?
1. 独立处理事件
若某个父容器不想让事件继续上传,可调用:
lv_event_stop_bubbling(e); // 类似“这事班主任处理了,不用上报校长”
2. 避免重复触发
例如子对象已经处理了点击事件,父容器无需重复响应。
总结:冒泡的四大优势
- 代码精简:父对象统一管理多个子对象事件
- 动态适配:适合运行时动态生成的控件
- 层级化管理:不同层级对象按职责处理事件
- 事件拦截:灵活控制事件传递范围
在LVGL中实现动态适配运行时生成控件的核心,可以类比为"搭积木"——父容器是地基,动态生成的控件是随时添加的积木块,而LVGL的机制能自动调整布局并管理交互。以下是具体实现原理和关键技术的通俗解析:
一、动态控件的"即插即用"机制
1. 运行时创建控件(搭积木)
- 代码示例:
c
复制
// 动态创建一个按钮并添加到父容器 lv_obj_t *new_btn = lv_btn_create(parent_container); lv_obj_set_size(new_btn, 80, 30); // 设置尺寸 lv_label_set_text(lv_label_create(new_btn), "动态按钮"); // 添加文字
- 关键点:通过
lv_btn_create()
等函数在代码运行时生成控件,无需预先设计。
- 关键点:通过
2. 自动布局管理(地基自适应)
- 布局引擎:父容器启用
LV_LAYOUT_FLEX
或LV_LAYOUT_GRID
,动态添加的子控件会自动排列。lv_obj_set_flex_flow(parent_container, LV_FLEX_FLOW_ROW_WRAP); // 横向流式布局
- 效果:无论添加多少按钮,父容器会自动调整大小和子控件位置,类似网页的弹性盒子布局。
二、事件处理的"智能路由"
1. 事件冒泡机制(层层上报)
- 原理:子控件的事件(如点击)会像气泡一样逐级传递给父容器,无需为每个动态控件单独绑定事件。
// 父容器统一处理所有子按钮的点击事件 lv_obj_add_event_cb(parent_container, parent_event_handler, LV_EVENT_CLICKED, NULL);
- 优势:即使动态生成100个按钮,只需在父级写一次事件处理逻辑。
2. 动态绑定数据(身份标识)
- 用户数据传递:创建控件时附加自定义数据(如ID),事件回调中识别来源。
// 创建按钮时传递ID int btn_id = 1001; lv_obj_add_event_cb(new_btn, btn_handler, LV_EVENT_CLICKED, (void*)btn_id); // 回调函数中读取ID void btn_handler(lv_event_t *e) { int id = (int)lv_event_get_user_data(e); // 获取按钮ID printf("按钮%d被点击", id); }
三、样式与数据的"实时同步"
1. 数据驱动更新(全局变量绑定)
- 实现逻辑:通过全局变量或回调函数,在数据变化时刷新控件。
int global_speed = 0; // 全局变量存储速度值 // 定时器回调中更新仪表盘指针 void timer_cb(lv_timer_t *timer) { lv_meter_set_indicator_value(ui.meter, ui.needle, global_speed++); }
- 应用场景:实时显示传感器数据、网络信息等动态内容。
2. 样式继承与覆盖(家族基因)
- 继承机制:动态控件未设置样式时,自动继承父容器的文本颜色、字体等属性。
// 父容器设置字体,子标签自动继承 lv_obj_set_style_text_font(parent_container, &my_font, LV_PART_MAIN);
四、性能优化技巧
1. 对象池技术(控件复用)
- 场景:频繁生成/销毁的控件(如聊天消息),可预先创建对象池,使用时激活而非重新创建。
lv_obj_t *msg_pool[10]; // 预创建10个消息框 void show_msg(const char *text) { lv_obj_t *msg = get_idle_msg_from_pool(); // 从池中取空闲对象 lv_label_set_text(msg, text); lv_obj_clear_flag(msg, LV_OBJ_FLAG_HIDDEN); // 显示 }
2. 异步加载(防止卡顿)
- 实现:耗时操作(如加载图片)放在定时器或任务中,避免阻塞主线程。
lv_timer_create(load_resources_task, 100, NULL); // 每100ms加载一部分资源
总结:动态适配的四大支柱
- 控件工厂模式:运行时通过API快速生成对象
- 智能事件路由:冒泡机制 + 数据标识实现高效交互
- 自适应布局引擎:流式/网格布局自动管理位置
- 数据绑定机制:全局变量 + 回调函数驱动内容更新
通过这套机制,开发者可以像"流水线生产"一样动态生成控件,同时保证界面自适应和交互流畅,非常适合需要实时更新数据的物联网设备、工业仪表盘等场景。