# LVGL_study **Repository Path**: L__Can/lvgl_study ## Basic Information - **Project Name**: LVGL_study - **Description**: 这个是lvgl的学习笔记 - **Primary Language**: C - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-29 - **Last Updated**: 2025-09-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LVGL 学习笔记 ## 目录 - [LVGL 学习笔记](#lvgl-学习笔记) - [目录](#目录) - [一、LVGL概念基础](#一lvgl概念基础) - [二、认识lvgl中的各种派生类](#二认识lvgl中的各种派生类) - [1、基础控件](#1基础控件) - [2、高级控件](#2高级控件) - [3、LVGL 8.3 存在但 v9 移除/替代的控件](#3lvgl-83-存在但-v9-移除替代的控件) - [4、LVGL v9 新增控件](#4lvgl-v9-新增控件) - [5、总结](#5总结) - [三、样式系统](#三样式系统) - [1、样式创建与管理](#1样式创建与管理) - [2、尺寸与位置](#2尺寸与位置) - [3、内边距与间距](#3内边距与间距) - [4、背景样式](#4背景样式) - [5、边框样式](#5边框样式) - [6、轮廓与阴影](#6轮廓与阴影) - [7、文本样式](#7文本样式) - [8、图像与线条](#8图像与线条) - [9、布局系统 (v9 重大更新)](#9布局系统-v9-重大更新) - [10、不透明度与混合](#10不透明度与混合) - [11、样式应用与对象操作](#11样式应用与对象操作) - [12、状态管理 (v9 重大更新)](#12状态管理-v9-重大更新) - [13、过渡效果](#13过渡效果) - [14、实用函数](#14实用函数) - [15、总结](#15总结) - [四、父对象与子对象的关系](#四父对象与子对象的关系) - [a、子对象的位置与大小](#a子对象的位置与大小) - [b、子对象的层级关系](#b子对象的层级关系) - [c、父对象的销毁与子对象的影响](#c父对象的销毁与子对象的影响) - [d、父对象的事件传递机制](#d父对象的事件传递机制) - [e、父对象的样式继承](#e父对象的样式继承) - [f、子对象的对齐](#f子对象的对齐) - [五、lvgl实践](#五lvgl实践) - [1、创建一个活动屏幕](#1创建一个活动屏幕) - [2、创建一个按钮](#2创建一个按钮) - [3、创建一个组对象](#3创建一个组对象) - [a、认识组的概念](#a认识组的概念) - [b、LVGL 组(Group)相关 API 列表](#blvgl-组group相关-api-列表) - [组创建与管理](#组创建与管理) - [对象管理](#对象管理) - [焦点控制](#焦点控制) - [输入设备绑定](#输入设备绑定) - [事件处理](#事件处理) - [状态常量(用于焦点状态)](#状态常量用于焦点状态) - [六、一个嵌入式lvgl架构](#六一个嵌入式lvgl架构) ## 一、LVGL概念基础 lvgl是基于部件概念上的设计来的一套ui设计架构。它提供了一组丰富的组件和工具,使得开发者能够快速构建出美观且高效的用户界面。这个部件的设计思想与c++中的类的思想相似,都是将数据和操作封装在一起,形成一个独立的模块。所以lvgl设计中,一个个部件就是一个个的类,一个个部件的创建就是一个个类的对象创建。
lvgl的部件,具有的属性有:样式、位置、大小等,这样就能把一个个部件抽象出lv_obj_t这个基础的类,从这个基础的类下就能实例化出父对象(这个父对象在lvgl中对应的是活动屏幕),再从这个lv_obj_t基类派生出其他具体的部件类。比如派生出lv_btn_t(按钮)、lv_label_t(标签)等。
你可能会奇怪,为什么有父对象,c++没有这个概念啊?其实这个父对象的概念就是为了实现部件的嵌套关系。比如一个按钮部件,它可能包含一个标签部件,这个标签部件就是按钮部件的子对象,而按钮部件就是标签部件的父对象。通过这种嵌套关系,就能实现复杂的UI布局和交互效果。父对象的概念是在创建具体部件(如一个按钮)时需要传入一个已经实例化的对象(比如说屏幕),这个时候就出现了一个层级结构,屏幕对象成为父对象,指向了一个子对象(按钮)。
## 二、认识lvgl中的各种派生类 ### 1、基础控件 | 控件类 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |-------------------|--------------------|----------|---------|------| | `lv_arc` | 弧形控件 | ✅ | ✅ | | | `lv_bar` | 进度条 | ✅ | ✅ | | | `lv_button` | 按钮 | ✅ | ✅ | v9 中优化样式 | | `lv_buttonmatrix` | 按钮矩阵 | ✅ | ✅ | | | `lv_canvas` | 画布(自定义绘图) | ✅ | ✅ | | | `lv_checkbox` | 复选框 | ✅ | ✅ | | | `lv_dropdown` | 下拉列表 | ✅ | ✅ | | | `lv_img` | 图像显示 | ✅ | ✅ | | | `lv_label` | 文本标签 | ✅ | ✅ | | | `lv_line` | 绘制线条 | ✅ | ✅ | | | `lv_roller` | 滚轮选择器 | ✅ | ✅ | | | `lv_slider` | 滑块 | ✅ | ✅ | | | `lv_switch` | 开关 | ✅ | ✅ | | | `lv_table` | 表格 | ✅ | ✅ | | | `lv_textarea` | 文本输入框 | ✅ | ✅ | | ### 2、高级控件 | 控件类 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------------|----------------|----------|---------|------| | `lv_chart` | 图表 | ✅ | ✅ | | | `lv_colorwheel`| 颜色选择轮盘 | ✅ | ✅ | | | `lv_keyboard` | 虚拟键盘 | ✅ | ✅ | | | `lv_led` | LED 指示灯 | ✅ | ✅ | | | `lv_meter` | 仪表盘 | ❌ | ✅ | **v9 新增**,替代 `lv_gauge` | | `lv_msgbox` | 消息弹窗 | ✅ | ✅ | | | `lv_spinbox` | 数字调节框 | ✅ | ✅ | | | `lv_spinner` | 加载动画 | ✅ | ✅ | | | `lv_tabview` | 标签页视图 | ✅ | ✅ | | | `lv_tileview` | 平铺视图 | ✅ | ✅ | | | `lv_win` | 窗口控件 | ✅ | ✅ | | ### 3、LVGL 8.3 存在但 v9 移除/替代的控件 | 控件类 | 描述 | LVGL 8.3 | LVGL v9 | 替代方案 | |-------------|--------------------|----------|---------|------------------------| | `lv_cont` | 容器(自动布局) | ✅ | ❌ | v9 使用 **Flex/Grid 布局** 直接应用于 `lv_obj` | | `lv_gauge` | 仪表盘(旧版) | ✅ | ❌ | 被 **`lv_meter`** 替代 | | `lv_list` | 列表 | ✅ | ❌ | 用 **`lv_menu`** + `lv_obj` 重构 | | `lv_page` | 滚动页面容器 | ✅ | ❌ | 滚动功能直接集成到 **`lv_obj`** | ### 4、LVGL v9 新增控件 | 控件类 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |--------------|----------------|----------|---------|------| | `lv_animimg` | 动画图像 | ❌ | ✅ | 支持帧动画 | | `lv_menu` | 多级菜单 | ❌ | ✅ | 替代旧版 `lv_list` | | `lv_fragment`| 片段(动态UI管理) | ❌ | ✅ | 用于复杂UI组织 | ### 5、总结 - **LVGL 8.3**:包含传统控件如 `lv_gauge`、`lv_list`、`lv_page`,但布局能力较弱。 - **LVGL v9**: - **移除**过时控件(如 `lv_cont`、`lv_page`),改为更灵活的 **Flex/Grid 布局**。 - **新增** `lv_meter`、`lv_menu`、`lv_animimg` 等现代控件。 - 所有控件继承自 `lv_obj`,通过 **事件+样式** 系统统一管理。 > 💡 建议:新项目优先使用 **LVGL v9**,布局和自定义能力更强。如需完整列表,请查阅 [LVGL 官方文档](https://docs.lvgl.io/)。 ## 三、样式系统 ### 1、样式创建与管理 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_init(style)` | 初始化样式对象 | ✅ | ✅ | 基本功能相同 | | `lv_style_copy(dest, src)` | 复制样式 | ✅ | ❌ | v9 移除,改用直接赋值 | | `lv_style_reset(style)` | 重置样式 | ❌ | ✅ | v9 新增 | | `lv_style_set_prop(style, prop, value)` | 设置样式属性 | ❌ | ✅ | v9 新增通用属性设置 | | `lv_style_get_prop(style, prop, value)` | 获取样式属性 | ❌ | ✅ | v9 新增 | | `lv_style_register_prop(flag)` | 注册自定义属性 | ❌ | ✅ | v9 新增高级功能 | ### 2、尺寸与位置 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_width(style, value)` | 设置宽度 | ✅ | ✅ | | | `lv_style_set_min_width(style, value)` | 设置最小宽度 | ❌ | ✅ | v9 新增 | | `lv_style_set_max_width(style, value)` | 设置最大宽度 | ❌ | ✅ | v9 新增 | | `lv_style_set_height(style, value)` | 设置高度 | ✅ | ✅ | | | `lv_style_set_min_height(style, value)` | 设置最小高度 | ❌ | ✅ | v9 新增 | | `lv_style_set_max_height(style, value)` | 设置最大高度 | ❌ | ✅ | v9 新增 | | `lv_style_set_x(style, value)` | 设置X坐标 | ✅ | ✅ | | | `lv_style_set_y(style, value)` | 设置Y坐标 | ✅ | ✅ | | | `lv_style_set_align(style, value)` | 设置对齐方式 | ✅ | ✅ | v9 增强功能 | | `lv_style_set_transform_*(style, value)` | 设置变换属性 | ❌ | ✅ | v9 新增变换功能 | ### 3、内边距与间距 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_pad_top(style, value)` | 设置上内边距 | ✅ | ✅ | | | `lv_style_set_pad_bottom(style, value)` | 设置下内边距 | ✅ | ✅ | | | `lv_style_set_pad_left(style, value)` | 设置左内边距 | ✅ | ✅ | | | `lv_style_set_pad_right(style, value)` | 设置右内边距 | ✅ | ✅ | | | `lv_style_set_pad_row(style, value)` | 设置行间距 | ❌ | ✅ | v9 新增 | | `lv_style_set_pad_column(style, value)` | 设置列间距 | ❌ | ✅ | v9 新增 | | `lv_style_set_pad_all(style, value)` | 设置所有内边距 | ✅ | ✅ | | | `lv_style_set_pad_hor(style, value)` | 设置水平内边距 | ✅ | ✅ | | | `lv_style_set_pad_ver(style, value)` | 设置垂直内边距 | ✅ | ✅ | | ### 4、背景样式 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_bg_color(style, color)` | 设置背景颜色 | ✅ | ✅ | | | `lv_style_set_bg_opa(style, opa)` | 设置背景透明度 | ✅ | ✅ | | | `lv_style_set_bg_grad_color(style, color)` | 设置渐变背景色 | ✅ | ✅ | | | `lv_style_set_bg_grad_dir(style, dir)` | 设置渐变方向 | ✅ | ✅ | | | `lv_style_set_bg_main_stop(style, value)` | 设置主色停止点 | ✅ | ✅ | | | `lv_style_set_bg_grad_stop(style, value)` | 设置渐变停止点 | ✅ | ✅ | | | `lv_style_set_bg_img_src(style, src)` | 设置背景图片 | ✅ | ✅ | | | `lv_style_set_bg_img_opa(style, opa)` | 设置背景图片透明度 | ✅ | ✅ | | | `lv_style_set_bg_img_recolor(style, color)` | 设置背景图片重着色 | ✅ | ✅ | | | `lv_style_set_bg_img_recolor_opa(style, opa)` | 设置重着色透明度 | ✅ | ✅ | | | `lv_style_set_bg_img_tiled(style, tiled)` | 设置背景图片平铺 | ✅ | ✅ | | ### 5、边框样式 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_border_color(style, color)` | 设置边框颜色 | ✅ | ✅ | | | `lv_style_set_border_opa(style, opa)` | 设置边框透明度 | ✅ | ✅ | | | `lv_style_set_border_width(style, width)` | 设置边框宽度 | ✅ | ✅ | | | `lv_style_set_border_side(style, side)` | 设置边框显示边 | ❌ | ✅ | v9 新增 | | `lv_style_set_border_post(style, post)` | 设置边框绘制顺序 | ✅ | ✅ | | ### 6、轮廓与阴影 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_outline_width(style, width)` | 设置轮廓宽度 | ✅ | ✅ | | | `lv_style_set_outline_color(style, color)` | 设置轮廓颜色 | ✅ | ✅ | | | `lv_style_set_outline_opa(style, opa)` | 设置轮廓透明度 | ✅ | ✅ | | | `lv_style_set_outline_pad(style, pad)` | 设置轮廓内边距 | ✅ | ✅ | | | `lv_style_set_shadow_width(style, width)` | 设置阴影宽度 | ✅ | ✅ | | | `lv_style_set_shadow_ofs_x(style, ofs)` | 设置阴影X偏移 | ✅ | ✅ | | | `lv_style_set_shadow_ofs_y(style, ofs)` | 设置阴影Y偏移 | ✅ | ✅ | | | `lv_style_set_shadow_spread(style, spread)` | 设置阴影扩散 | ✅ | ✅ | | | `lv_style_set_shadow_color(style, color)` | 设置阴影颜色 | ✅ | ✅ | | | `lv_style_set_shadow_opa(style, opa)` | 设置阴影透明度 | ✅ | ✅ | | ### 7、文本样式 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_text_color(style, color)` | 设置文本颜色 | ✅ | ✅ | | | `lv_style_set_text_opa(style, opa)` | 设置文本透明度 | ✅ | ✅ | | | `lv_style_set_text_font(style, font)` | 设置文本字体 | ✅ | ✅ | | | `lv_style_set_text_letter_space(style, space)` | 设置字母间距 | ✅ | ✅ | | | `lv_style_set_text_line_space(style, space)` | 设置行间距 | ✅ | ✅ | | | `lv_style_set_text_decor(style, decor)` | 设置文本装饰 | ✅ | ✅ | | | `lv_style_set_text_align(style, align)` | 设置文本对齐 | ✅ | ✅ | | ### 8、图像与线条 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_img_opa(style, opa)` | 设置图像透明度 | ✅ | ✅ | | | `lv_style_set_img_recolor(style, color)` | 设置图像重着色 | ✅ | ✅ | | | `lv_style_set_img_recolor_opa(style, opa)` | 设置重着色透明度 | ✅ | ✅ | | | `lv_style_set_line_width(style, width)` | 设置线条宽度 | ✅ | ✅ | | | `lv_style_set_line_dash_width(style, width)` | 设置虚线宽度 | ✅ | ✅ | | | `lv_style_set_line_dash_gap(style, gap)` | 设置虚线间隔 | ✅ | ✅ | | | `lv_style_set_line_rounded(style, rounded)` | 设置线条圆角 | ✅ | ✅ | | | `lv_style_set_line_color(style, color)` | 设置线条颜色 | ✅ | ✅ | | | `lv_style_set_line_opa(style, opa)` | 设置线条透明度 | ✅ | ✅ | | ### 9、布局系统 (v9 重大更新) | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_layout(style, layout)` | 设置布局类型 | ❌ | ✅ | v9 新增 | | `lv_style_set_flex_flow(style, flow)` | 设置弹性流方向 | ❌ | ✅ | v9 新增 | | `lv_style_set_flex_main_place(style, place)` | 设置主轴对齐 | ❌ | ✅ | v9 新增 | | `lv_style_set_flex_cross_place(style, place)` | 设置交叉轴对齐 | ❌ | ✅ | v9 新增 | | `lv_style_set_flex_track_place(style, place)` | 设置轨道对齐 | ❌ | ✅ | v9 新增 | | `lv_style_set_flex_grow(style, grow)` | 设置弹性增长 | ❌ | ✅ | v9 新增 | | `lv_style_set_grid_*(style, ...)` | 网格布局相关 | ❌ | ✅ | v9 新增网格布局系统 | ### 10、不透明度与混合 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_opa(style, opa)` | 设置整体透明度 | ✅ | ✅ | | | `lv_style_set_opa_layered(style, opa)` | 设置分层透明度 | ❌ | ✅ | v9 新增 | | `lv_style_set_blend_mode(style, mode)` | 设置混合模式 | ✅ | ✅ | v9 增强功能 | ### 11、样式应用与对象操作 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_obj_add_style(obj, style, selector)` | 添加样式到对象 | ❌ | ✅ | v9 新增 | | `lv_obj_remove_style(obj, style, selector)` | 移除对象样式 | ❌ | ✅ | v9 新增 | | `lv_obj_remove_style_all(obj)` | 移除所有样式 | ❌ | ✅ | v9 新增 | | `lv_obj_refresh_style(obj, part, prop)` | 刷新对象样式 | ❌ | ✅ | v9 新增 | | `lv_obj_get_style_prop(obj, part, prop)` | 获取计算样式 | ❌ | ✅ | v9 新增 | | `lv_obj_set_style_local_*(obj, part, state, ...)` | 设置本地样式 | ✅ | ❌ | 8.3 特有方式 | | `lv_obj_set_style(obj, style)` | 设置对象样式 | ✅ | ❌ | 8.3 方式 | ### 12、状态管理 (v9 重大更新) | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_obj_add_state(obj, state)` | 添加状态 | ❌ | ✅ | v9 新增 | | `lv_obj_clear_state(obj, state)` | 清除状态 | ❌ | ✅ | v9 新增 | | `lv_obj_get_state(obj)` | 获取状态 | ❌ | ✅ | v9 新增 | | `lv_obj_has_state(obj, state)` | 检查状态 | ❌ | ✅ | v9 新增 | ### 13、过渡效果 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_set_transition(style, trans)` | 设置过渡效果 | ✅ | ✅ | v9 参数类型变化 | | `lv_style_transition_dsc_init(tr, props, path, time, delay, user_data)` | 初始化过渡描述 | ❌ | ✅ | v9 新增 | ### 14、实用函数 | API 函数 | 描述 | LVGL 8.3 | LVGL v9 | 备注 | |----------|------|-----------|---------|------| | `lv_style_value_color(color)` | 创建颜色值 | ❌ | ✅ | v9 新增 | | `lv_style_value_opa(opa)` | 创建透明度值 | ❌ | ✅ | v9 新增 | | `lv_style_value_ptr(ptr)` | 创建指针值 | ❌ | ✅ | v9 新增 | | `lv_style_prop_get_name(prop)` | 获取属性名 | ❌ | ✅ | v9 新增 | ### 15、总结 你可以看到,LVGL v9 在样式系统上进行了大量改进和优化,新增了许多强大的功能,如 Flex/Grid 布局、状态管理和更灵活的样式应用方式。引入的状态(state)和部件(part)的概念,使得样式可以针对对象的不同状态(如按下、禁用)和不同部件(如滑块、滚动条)进行设置,大大提升了样式的灵活性和可维护性。你可以从下面的代码中看出v8.3跟v9版本的差异
```c // LVGL 8.3 设置按钮样式 lv_style_t style; lv_style_init(&style); lv_style_set_bg_color(&style, LV_STATE_DEFAULT, LV_COLOR_RED); lv_style_set_bg_opa(&style, LV_STATE_DEFAULT, LV_OPA_COVER); lv_obj_set_style(btn, &style); // 设置整个对象的样式 // LVGL 9.0 设置按钮样式 lv_style_t style; lv_style_init(&style); lv_style_set_bg_color(&style, LV_COLOR_RED); lv_style_set_bg_opa(&style, LV_OPA_COVER); lv_obj_add_style(btn, &style, LV_PART_MAIN | LV_STATE_DEFAULT); // 仅应用于主部件的默认状态 ``` 你可以看到```void lv_obj_add_style(lv_obj_t * obj, lv_style_t * style, lv_style_selector_t selector);```这个api增加了一个lv_style_selector_t类型的实参,这个是样式的选择器(selector),它其实就是用来指定样式应用到对象的哪个部分或状态的。比如可以通过选择器来指定样式应用到按钮的默认状态、按下状态或禁用状态等,这样就能实现不同状态下的样式变化。这个功能最大的作用是优化样式的复用性和维护性。通过选择器,可以将通用的样式定义在一个地方,然后根据需要应用到不同的对象和状态上,避免了重复定义样式的麻烦,在旧的lvgl版本中经常要对ui中的不同事件(event)做样式的变化,如焦点改变、按钮按下等情景应用不同的样式,进而导致需要在代码的各个地方去定义当前需要的样式,样式的管理非常复杂。 ## 四、父对象与子对象的关系 ### a、子对象的位置与大小 子对象的位置是相对于父对象的坐标系来定义的。也就是说,子对象的坐标原点(0,0)是父对象的左上角,而不是屏幕的左上角。而相应的子对象的所处坐标位置如果超出父对象的大小时,lvgl中也是不会显示这个子对象的。如果这个父对象是一个活动屏幕时,那么子对象就对应的不会在实际的lcd屏幕中显示出来,但是你却是可以在内存中找到这个子对象的。我们再深入一下父对象的大小这个问题,如果子对象超出了父对象的大小时,子对象是不会显示出来的,这个是因为lvgl中有一个裁剪区域的概念。裁剪区域就是父对象的可视区域,只有在这个区域内的子对象才会被渲染和显示出来。如果子对象超出了父对象的裁剪区域,那么超出部分就会被裁剪掉,不会显示出来。这个裁剪区域是由父对象的大小和位置决定的,所以如果父对象的大小发生变化,裁剪区域也会相应地变化,进而影响子对象的显示。
这个裁剪区域的概念对于嵌套的父子对象关系来说是非常重要的,因为它决定了子对象的可见性和显示效果。通过合理地设置父对象的大小和位置,可以实现对子对象的显示进行精细的控制,比如实现滚动视图、分页显示等效果。可以做这样一个例子:
在当前的活动屏幕上创建一个屏幕对象作为父对象scr_1,然后在这个容器中往y轴添加多个子对象使得超出父对象的大小,然后通过```lv_obj_scroll_y()```函数来实现垂直滚动效果。
这个例子说明了父对象与子对象的关系,以及裁剪区域的概念。父对象scr_1的大小和位置的属性实际是自己在屏幕这个顶级父对象上的渲染大小和位置,而不是父对象scr_1自己所有存在的内容实际使用的空间大小。如果父对象的使用的空间大小大于父对象的渲染大小和位置时,父对象的窗口部分是不会显示出来的。但是不显示的区域并不是消失了,而是存在于内存中,只是没有被渲染到屏幕上而已。这个概念对于理解lvgl的渲染机制和父子对象关系是非常重要的。对于没有渲染的区域,你可以通过操作滚动条(lv_obj_scroll_y()、lv_obj_scroll_x())来查看这些区域的内容。
> 💡 注意:父对象scr_1能够使用的空间大小只被int32限制(你可以试着使用lv_obj_set_x()来设置子对象位置),但是父对象的scr_1的渲染大小却被物理世界中的屏幕大小所限制,这也是出现渲染区域的原因。所以千万不要认为lv_obj_t中的大小属性是使用的空间大小,这不对哈。 ### b、子对象的层级关系 嵌套的子对象关系中,子对象的层级关系是由它们在父对象中的添加顺序决定的。后添加的子对象会覆盖在先添加的子对象之上,形成一个层级结构。这个层级结构决定了子对象的显示顺序和遮挡关系。比如在一个父对象中,如果先添加了一个按钮A,然后又添加了一个按钮B,那么按钮B会覆盖在按钮A之上。如果按钮B的位置与按钮A重叠,那么按钮A就会被按钮B遮挡住,无法看到。同样,如果再添加一个标签C,这个标签C会覆盖在按钮B之上,形成更高的层级。
这个层级关系对于实现复杂的UI布局和交互效果是非常重要的。通过合理地安排子对象的添加顺序,可以实现不同的显示效果和交互逻辑,比如实现弹出菜单、模态对话框等效果。你可以通过```lv_obj_move_foreground()```和```lv_obj_move_background()```函数来调整子对象的层级关系,使得某个子对象能够覆盖在其他子对象之上或者被其他子对象覆盖。
### c、父对象的销毁与子对象的影响 在嵌套的父子对象关系中,父对象的销毁会影响到它的所有子对象。当一个父对象被销毁时,它的所有子对象也会被自动销毁,释放它们占用的内存资源。这是因为子对象是依赖于父对象存在的,如果父对象不存在了,那么子对象也就失去了存在的意义。
这个机制对于内存管理和资源释放是非常重要的。通过自动销毁子对象,可以避免内存泄漏和资源浪费,确保系统的稳定性和性能。当不再需要一个父对象时,只需要销毁它,所有相关的子对象也会被自动清理掉,简化了内存管理的复杂性。
你可以通过```lv_obj_del()```函数来销毁一个父对象,这个函数会递归地销毁父对象及其所有子对象,确保所有相关的资源都被正确释放。
### d、父对象的事件传递机制 在嵌套的父子对象关系中,事件传递机制决定了事件如何在父对象和子对象之间传播。当一个事件发生在一个子对象上时,这个事件会首先被子对象处理,如果子对象没有处理这个事件,那么这个事件会被传递给它的父对象,依此类推,直到事件被处理或者到达顶级父对象为止。
这个机制对于实现复杂的交互逻辑和事件处理是非常重要的。通过事件传递,可以实现事件的冒泡和捕获,使得不同层级的对象能够协同处理事件。比如一个按钮被点击时,按钮本身可以处理点击事件,如果按钮没有处理这个事件,那么这个事件会传递给它的父对象(比如一个屏幕),父对象可以根据需要处理这个事件,比如显示一个弹出菜单。
你可以通过```lv_obj_add_event_cb()```函数来为父对象和子对象添加事件回调函数,处理不同的事件类型。通过合理地设计事件传递机制,可以实现灵活的交互效果和用户体验。
### e、父对象的样式继承 在嵌套的父子对象关系中,样式继承机制决定了子对象如何继承和应用父对象的样式。当一个子对象没有定义某个样式属性时,它会自动继承父对象的对应样式属性。如果子对象定义了这个样式属性,那么子对象会使用自己的样式属性,而不是继承父对象的样式属性。
这个机制对于实现一致的UI风格和样式管理是非常重要的。通过样式继承,可以确保子对象在没有显式定义样式时,能够保持与父对象一致的外观和感觉。比如一个按钮的父对象是一个屏幕,如果屏幕定义了背景颜色为蓝色,那么按钮如果没有定义背景颜色,它会自动继承屏幕的背景颜色,显示为蓝色。
你可以通过```lv_obj_add_style()```函数来为父对象和子对象添加样式,确保它们具有一致的外观和感觉。通过合理地设计样式继承机制,可以简化样式管理,提高UI的一致性和可维护性。
### f、子对象的对齐 在嵌套的父子对象关系中,子对象的对齐方式决定了子对象在父对象中的位置和布局。通过设置子对象的对齐方式,可以实现不同的布局效果,比如居中对齐、左对齐、右对齐等。
你可以通过```lv_obj_align()```函数来设置子对象的对齐方式,这个函数允许你指定子对象相对于父对象的位置和对齐方式。比如你可以将一个按钮居中对齐在它的父对象中,或者将一个标签左对齐在它的父对象中。
通过合理地设置子对象的对齐方式,可以实现灵活的布局效果和用户体验。比如在一个屏幕中,你可以将一个标题标签居中对齐在屏幕的顶部,将一个按钮左对齐在屏幕的底部,实现一个典型的UI布局。
注意这个子对象的对齐,如果子对象的大小超出了父对象的大小时,对齐会受到影响,超出部分不被渲染。而且这个对齐实际的含义是改变子对象计算坐标原点的位置,假设设置为居中对齐,那就是从默认的以父对象的左上角作为坐标(0,0),改成以父对象的居中的坐标点为子对象的坐标原点(0,0),所以在叠加上对齐方式后,子对象在屏幕中的默认渲染位置就改变了,所以计算子对象的坐标以实现ui效果就会麻烦(反正我是晕的,一定要模拟器参与,一边改一边看效果)
## 五、lvgl实践 ### 1、创建一个活动屏幕 在创建活动屏幕之前,我们再重新认识一下lv_obj_t这个基础类。lv_obj_t这个类是所有部件的基类,所有的部件都是从这个类派生出来的。这个类中包含了所有部件共有的属性和方法,比如位置、大小、样式等。这个类中还包含了一个指向父对象的指针,这个父对象就是这个部件所在的容器。比如一个按钮部件,它的父对象就是它所在的屏幕或者它所在的容器。也就是说如果以下面的代码创建的一个基础部件就是活动屏幕:
```c lv_obj_t * scr = lv_obj_create(NULL); // 创建一个新的对象,父对象为 NULL,表示这是一个顶级对象(活动屏幕) lv_scr_load(scr); // 将创建的对象设置为当前活动屏幕,之所以有这个是因为在lv_init()函数中已经创建了一个默认的活动屏幕,所以这里需要切换到我们新创建的屏幕 lv_obj_t * src_2 = lv_obj_create(lv_scr_act()); // 创建一个新的对象,父对象为当前活动屏幕 ``` 你可以看到上面的代码中,首先创建了一个新的对象,这个对象的父对象为NULL,表示这是一个顶级对象(活动屏幕)。然后通过`lv_scr_load`函数将这个新创建的对象设置为当前活动屏幕。接着又创建了一个新的对象,这个对象的父对象为当前活动屏幕。这样就形成了一个层次结构,活动屏幕作为顶级对象,再创建一个屏幕部件作为它的子对象。转化到实际效果上就是:在底层的活动屏幕scr之上还有一个新的屏幕src_2,如下图。
![alt text](image.png)
### 2、创建一个按钮 在创建一个按钮之前,我们先来认识一下lv_btn_t这个类。lv_btn_t这个类是从lv_obj_t这个基础类派生出来的,表示一个按钮部件。这个类中包含了按钮特有的属性和方法,比如按下状态、释放状态等。这个类中还定义了一个指向标签部件的指针,这个标签部件就是按钮上的文本标签。也就是说一个按钮部件实际上是由一个基础部件和一个标签部件组成的。我们可以通过以下代码来创建一个按钮:
```c lv_btn_t * btn = lv_btn_create(lv_scr_act()); // 创建一个新的按钮,父对象为当前活动屏幕 lv_obj_set_size(btn, 100, 50); // 设置按钮的大小 lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // 设置按钮的对齐方式 lv_obj_t * label = lv_label_create(btn); // 在按钮上创建一个标签 lv_label_set_text(label, "Button"); // 设置标签的文本 lv_obj_center(label); // 将标签居中对齐 ``` ### 3、创建一个组对象 #### a、认识组的概念 在创建一个组对象之前,我们先来认识一下lv_group_t这个类。lv_group_t这个类表示一个组对象,组对象是用来管理一组可聚焦的部件的。通过将部件添加到组对象中,可以实现键盘导航和触摸屏导航等功能。我们可以通过以下代码来创建一个组对象:
```c //创建一个文本部件 lv_obj_t * label = lv_label_create(lv_scr_act()); lv_label_set_text(label, "Hello, LVGL!"); //创建一个按钮部件 lv_obj_t * btn = lv_btn_create(lv_scr_act()); lv_obj_set_size(btn, 100, 50); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); //创建一个组对象 lv_group_t * group = lv_group_create(); lv_group_add_obj(group, btn); lv_group_add_obj(group, label); //设置组对象为当前活动组 lv_group_set_default(group); //设置聚焦到按钮部件 lv_group_focus_obj(btn); //设置聚焦到标签部件 lv_group_focus_obj(label); ``` 你上面看到我把lvgl中组的概念称作可用来管理一组可聚焦部件的,事实上lvgl中的组对象不仅仅是用来管理可聚焦部件的,它还可以用来管理一组部件的样式和状态。通过将部件添加到组对象中,可以实现对一组部件的统一管理,比如设置它们的样式、状态等。这样就可以简化代码,提高代码的可维护性。
比如说下面的代码: ```c //创建一个文本部件 lv_obj_t * label = lv_label_create(lv_scr_act()); lv_label_set_text(label, "Hello, LVGL!"); //创建一个按钮部件 lv_obj_t * btn = lv_btn_create(lv_scr_act()); lv_obj_set_size(btn, 100, 50); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); //创建一个组对象 lv_group_t * group = lv_group_create(); lv_group_add_obj(group, btn); lv_group_add_obj(group, label); //设置组对象为当前活动组 lv_group_set_default(group); //设置组对象的样式 lv_obj_set_style_bg_color((lv_obj_t *)group, lv_color_hex(0xFF0000), 0); // 设置组对象的背景颜色为红色 lv_obj_set_style_bg_opa((lv_obj_t *)group, LV_OPA_COVER, 0); // 设置组对象的背景不透明 lv_obj_set_style_border_color((lv_obj_t *)group, lv_color_hex(0x00FF00), 0); // 设置组对象的边框颜色为绿色 lv_obj_set_style_border_width((lv_obj_t *)group, 2, 0); // 设置组对象的边框宽度为2像素 lv_obj_set_style_pad_all((lv_obj_t *)group, 10, 0); // 设置组对象的内边距为10像素 lv_obj_set_style_radius((lv_obj_t *)group, 5, 0); // 设置组对象的圆角半径为5像素 lv_obj_set_style_shadow_color((lv_obj_t *)group, lv_color_hex(0x0000FF), 0); // 设置组对象的阴影颜色为蓝色 lv_obj_set_style_shadow_opa((lv_obj_t *)group, LV_OPA_50, 0); // 设置组对象的阴影不透明度为50% lv_obj_set_style_shadow_width((lv_obj_t *)group, 5, 0); // 设置组对象的阴影宽度为5像素 lv_obj_set_style_shadow_ofs_x((lv_obj_t *)group, 3, 0); // 设置组对象的阴影X偏移为3像素 lv_obj_set_style_shadow_ofs_y((lv_obj_t *)group, 3, 0); // 设置组对象的阴影Y偏移为3像素 lv_obj_set_style_text_color((lv_obj_t *)group, lv_color_hex(0xFFFFFF), 0); // 设置组对象的文本颜色为白色 lv_obj_set_style_text_font((lv_obj_t *)group, &lv_font_montserrat_16, 0); // 设置组对象的文本字体为Montserrat 16px lv_obj_set_style_text_align((lv_obj_t *)group, LV_TEXT_ALIGN_CENTER, 0); // 设置组对象的文本对齐方式为居中 lv_obj_set_style_text_letter_space((lv_obj_t *)group, 2, 0); // 设置组对象的文本字母间距为2像素 lv_obj_set_style_text_line_space((lv_obj_t *)group, 4, 0); // 设置组对象的文本行间距为4像素 ``` #### b、LVGL 组(Group)相关 API 列表 ##### 组创建与管理 | 函数原型 | 描述 | LVGL 8.3 | LVGL v9 | |----------|------|-----------|---------| | `lv_group_t * lv_group_create(void)` | 创建新组 | ✅ | ✅ | | `void lv_group_del(lv_group_t * group)` | 删除组 | ✅ | ✅ | | `void lv_group_set_default(lv_group_t * group)` | 设置默认组 | ✅ | ✅ | | `lv_group_t * lv_group_get_default(void)` | 获取默认组 | ✅ | ✅ | | `void lv_group_set_editing(lv_group_t * group, bool edit)` | 设置编辑模式 | ✅ | ✅ | | `bool lv_group_get_editing(const lv_group_t * group)` | 获取编辑模式状态 | ✅ | ✅ | | `void lv_group_set_wrap(lv_group_t * group, bool en)` | 设置循环导航 | ✅ | ✅ | | `bool lv_group_get_wrap(lv_group_t * group)` | 获取循环导航状态 | ✅ | ✅ | | `void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb)` | 设置焦点改变回调 | ✅ | ✅ | | `void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy)` | 设置重新聚焦策略 | ❌ | ✅ | | `void lv_group_set_click_focus(lv_group_t * group, bool en)` | 设置点击聚焦 | ❌ | ✅ | | `void lv_group_set_focus_freezer(lv_group_t * group, bool en)` | 设置焦点冻结 | ❌ | ✅ | | `void lv_group_focus_freeze(lv_group_t * group, bool freeze)` | 冻结/解冻焦点 | ❌ | ✅ | | `void lv_group_update_focus(const lv_group_t * group)` | 强制更新焦点状态 | ❌ | ✅ | ##### 对象管理 | 函数原型 | 描述 | LVGL 8.3 | LVGL v9 | |----------|------|-----------|---------| | `void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj)` | 添加对象到组 | ✅ | ✅ | | `void lv_group_remove_obj(lv_obj_t * obj)` | 从组中移除对象 | ✅ | ✅ | | `void lv_group_remove_all_objs(lv_group_t * group)` | 移除组内所有对象 | ✅ | ✅ | | `void lv_group_swap_obj(lv_obj_t * obj1, lv_obj_t * obj2)` | 交换两个对象位置 | ❌ | ✅ | | `void lv_group_move_obj_to_index(lv_obj_t * obj, uint32_t index)` | 移动对象到指定位置 | ❌ | ✅ | | `uint32_t lv_group_get_obj_count(lv_group_t * group)` | 获取组内对象数量 | ✅ | ✅ | | `lv_obj_t * lv_group_get_obj_by_index(lv_group_t * group, uint32_t index)` | 按索引获取对象 | ❌ | ✅ | ##### 焦点控制 | 函数原型 | 描述 | LVGL 8.3 | LVGL v9 | |----------|------|-----------|---------| | `void lv_group_focus_obj(lv_obj_t * obj)` | 聚焦特定对象 | ✅ | ✅ | | `void lv_group_focus_next(lv_group_t * group)` | 聚焦下一个对象 | ✅ | ✅ | | `void lv_group_focus_prev(lv_group_t * group)` | 聚焦上一个对象 | ✅ | ✅ | | `void lv_group_focus_first(lv_group_t * group)` | 聚焦第一个对象 | ❌ | ✅ | | `void lv_group_focus_last(lv_group_t * group)` | 聚焦最后一个对象 | ❌ | ✅ | | `lv_obj_t * lv_group_get_focused(const lv_group_t * group)` | 获取当前焦点对象 | ✅ | ✅ | | `uint32_t lv_group_get_focused_index(const lv_group_t * group)` | 获取焦点对象索引 | ❌ | ✅ | | `bool lv_group_obj_is_focused(lv_obj_t * obj)` | 检查对象是否聚焦 | ❌ | ✅ | ##### 输入设备绑定 | 函数原型 | 描述 | LVGL 8.3 | LVGL v9 | |----------|------|-----------|---------| | `void lv_indev_set_group(lv_indev_t * indev, lv_group_t * group)` | 绑定输入设备到组 | ✅ | ✅ | | `lv_group_t * lv_indev_get_group(const lv_indev_t * indev)` | 获取输入设备绑定的组 | ✅ | ✅ | | `void lv_indev_set_button_points(lv_indev_t * indev, const lv_point_t points[])` | 设置按钮映射点 | ✅ | ✅ | | `const lv_point_t * lv_indev_get_button_points(lv_indev_t * indev)` | 获取按钮映射点 | ✅ | ✅ | ##### 事件处理 | 函数原型 | 描述 | LVGL 8.3 | LVGL v9 | |----------|------|-----------|---------| | `void lv_group_send_data(lv_group_t * group, uint32_t c)` | 发送数据到当前焦点对象 | ✅ | ✅ | | `void lv_obj_add_state(lv_obj_t * obj, lv_state_t state)` | 添加状态(如聚焦状态) | ❌ | ✅ | | `void lv_obj_clear_state(lv_obj_t * obj, lv_state_t state)` | 清除状态 | ❌ | ✅ | | `lv_state_t lv_obj_get_state(const lv_obj_t * obj)` | 获取对象状态 | ❌ | ✅ | | `bool lv_obj_has_state(const lv_obj_t * obj, lv_state_t state)` | 检查对象是否具有特定状态 | ❌ | ✅ | ##### 状态常量(用于焦点状态) | 常量 | 描述 | LVGL 8.3 | LVGL v9 | |------|------|-----------|---------| | `LV_STATE_FOCUSED` | 对象被聚焦 | ✅ | ✅ | | `LV_STATE_FOCUS_KEY` | 通过键盘聚焦 | ❌ | ✅ | | `LV_STATE_EDITED` | 对象正在编辑 | ❌ | ✅ | | `LV_STATE_CHECKED` | 对象被选中 | ✅ | ✅ | | `LV_STATE_PRESSED` | 对象被按下 | ✅ | ✅ | ## 六、一个嵌入式lvgl架构 好了,现在开始搞一个健壮性良好的嵌入式lvgl架构。首先是设计思路上:我们可以借鉴前后端的解耦设计,ui作为前端,而其余的逻辑部分作为后端。前端只负责ui的展示和用户交互,后端负责业务逻辑和数据处理。前端通过事件回调函数将用户的操作传递给后端,后端处理完业务逻辑后,再通过回调函数将结果传递给前端进行展示。这样就实现了前后端的解耦,提高了代码的可维护性和可扩展性。
这样简单的前后端隔离的设计看着就很简洁,但是在面对一些情况时会显得力不从心:只是单纯的前后端分割,前端调用后端的函数,后端调用前端的函数,会导致代码耦合度还是太高,代码在各个文件中到处直接调用前端或者后端函数,会导致代码难以阅读,维护性差,假如这些函数没有定义一系列规范的接口函数那更是阅读灾难。我们可以通过引入一个中间层来进一步解耦前后端的关系。这个中间层可以是一个事件总线,前端通过事件总线将用户的操作传递给后端,后端通过事件总线将结果传递给前端。这样前后端之间就没有直接的调用关系,代码的耦合度大大降低,提高了代码的可维护性和可扩展性。
下面是一个简单的嵌入式lvgl架构示意图:
[->点击这里获取系统框图文件.drawio<-](./前后端分离的系统框图实例.drawio)
![alt text](image-1.png) 可以看到,上面的图中不仅把前端ui与后端分离开了,而且还把后端的各种服务也分离开了,且ui的各个部件不会直接调用收集与分发模块的函数,而是通过一组接口调用到收集与分发模块的函数,来解耦ui与收集模块,这样代码就非常清晰。而后端服务也是如此,通过一组接口函数来与收集与分发模块解耦。