一、简介
前面的介绍中,我们实现的页面都是比较简单的。当要实现一个复杂的页面的时候,只考虑widget就不够了,需要将widget进行更细的拆分。那就是我们今天说的part和state.
二、名词介绍
widget(控件)
widget就是LVGL中的基本单元,例如,一个 按钮、标签、图像、列表、图表 或者 文本区域。我们之前介绍了object,里面有对widget的介绍,如果忘记了的话,可以复习一下。https://blog.csdn.net/qq_26226375/article/details/162847427?spm=1001.2014.3001.5502
part(部分)
part是对widget的详细拆分。例如一个slider(滑块)分为轨道、进度条、按钮。轨道、进度条、按钮这些就是slider的part。
先看一下LVGL中part一共有哪些。
cpp
typedef enum {
LV_PART_MAIN = 0x000000, // 主体背景,类似矩形的背景区域
LV_PART_SCROLLBAR = 0x010000, // 滚动条
LV_PART_INDICATOR = 0x020000, // 指示器,例如滑块、进度条、开关的指示部分,或复选框的勾选框
LV_PART_KNOB = 0x030000, // 旋钮/手柄,用来拖动调节数值的把手
LV_PART_SELECTED = 0x040000, // 选中项,表示当前被选中的选项或区域
LV_PART_ITEMS = 0x050000, // 子项集合,用于控件包含多个相似元素时(如表格单元格)
LV_PART_CURSOR = 0x060000, // 光标,标记特定位置,如文本框的光标或图表上的标记
LV_PART_CUSTOM_FIRST = 0x080000, // 自定义部件起点,供自定义控件扩展用
LV_PART_ANY = 0x0F0000, // 通配符,可在部分函数中用于指向所有部件
} lv_part_t;
这个是官方的介绍,看不懂没关系,后面我们还会再说的。
那为什么要用part呢?
正常产品都需要先经过UI设计,然后再实现。而UI设计一般都会改变控件原有的颜色。例如slider的轨道要无色,进度条要红色,按钮要蓝色等等。所以LVGL提供了part这个概念来供程序员改变不同的part。
那这里就会有个疑问。lv_part_t有这么多元素,意思是每个控件都有这个多part吗?
当然不是。其实每个控件可操作的part是不一样的,而且相同的part在不同的控件里表示的含义也是不同。这里我总结一下。
widget与part对应表
基础控件
| Widget | Part | 代表区域 |
|---|---|---|
lv_obj |
LV_PART_MAIN | 主体背景矩形 |
| LV_PART_SCROLLBAR | 滚动条 | |
lv_button |
LV_PART_MAIN | 按钮圆角矩形本体 |
lv_label |
LV_PART_MAIN | 文字所在的背景区 |
| LV_PART_SCROLLBAR | 文字超长时的滚动条 | |
| LV_PART_SELECTED | 文字选中高亮区 | |
lv_line |
LV_PART_MAIN | 直线本体 |
lv_image |
LV_PART_MAIN | 图片本体 |
lv_led |
LV_PART_MAIN | LED 灯本体 |
lv_canvas |
LV_PART_MAIN | 画布本体 |
滑动 / 调节类
| Widget | Part | 代表区域 |
|---|---|---|
lv_slider |
LV_PART_MAIN | 整条轨道的背景矩形 |
| LV_PART_INDICATOR | 已填充的那段(从左到当前值) | |
| LV_PART_KNOB | 可拖动的圆点旋钮 | |
lv_bar |
LV_PART_MAIN | 进度条背景槽 |
| LV_PART_INDICATOR | 已填充的进度条 | |
lv_switch |
LV_PART_MAIN | 开关底座背景 |
| LV_PART_INDICATOR | 打开时露出的背景(开启色) | |
| LV_PART_KNOB | 可滑动的圆点 | |
lv_arc |
LV_PART_MAIN | 圆弧背景(整圈底色) |
| LV_PART_INDICATOR | 值对应的圆弧段 | |
| LV_PART_KNOB | 圆弧末端的旋钮点 | |
lv_scale |
LV_PART_MAIN | 刻度尺主体背景 |
| LV_PART_INDICATOR | 主刻度线 | |
| LV_PART_ITEMS | 次刻度线 / 刻度标签 | |
lv_spinbox |
LV_PART_MAIN | 主体背景 |
| LV_PART_CURSOR | 数字输入光标 |
文本输入类
| Widget | Part | 代表区域 |
|---|---|---|
lv_textarea |
LV_PART_MAIN | 输入框主体(白底边框区) |
| LV_PART_SCROLLBAR | 滚动条 | |
| LV_PART_CURSOR | 文字闪烁光标竖线 | |
| LV_PART_SELECTED | 选中文本的高亮底色 | |
lv_keyboard |
LV_PART_MAIN | 键盘背景 |
| LV_PART_ITEMS | 每个按键 | |
lv_ime_pinyin |
LV_PART_MAIN | 候选词栏背景 |
| LV_PART_ITEMS | 候选词 / 按键 |
选择 / 列表类
| Widget | Part | 代表区域 |
|---|---|---|
lv_checkbox |
LV_PART_MAIN | 文字及整体背景 |
| LV_PART_INDICATOR | 左侧勾选方框 | |
lv_dropdown |
LV_PART_MAIN | 下拉框按钮本体 |
| LV_PART_INDICATOR | 右侧下拉箭头图标 | |
| LV_PART_SELECTED | 当前选中项 | |
| LV_PART_ITEMS | 展开列表中的每一项 | |
lv_roller |
LV_PART_MAIN | 滚轮主体背景 |
| LV_PART_SELECTED | 当前选中行的高亮区 | |
| LV_PART_ITEMS | 可见的每一行 | |
lv_buttonmatrix |
LV_PART_MAIN | 矩阵整体背景 |
| LV_PART_ITEMS | 每个按钮单元 | |
lv_table |
LV_PART_MAIN | 表格主体背景 |
| LV_PART_ITEMS | 每个单元格 | |
| LV_PART_SCROLLBAR | 滚动条 |
容器 / 布局类
| Widget | Part | 代表区域 |
|---|---|---|
lv_list |
LV_PART_MAIN | 列表主体背景 |
| LV_PART_SCROLLBAR | 滚动条 | |
lv_menu |
LV_PART_MAIN | 菜单主体 |
| LV_PART_SCROLLBAR | 滚动条 | |
lv_msgbox |
LV_PART_MAIN | 消息框背景 |
| LV_PART_ITEMS | 消息框内的按钮 | |
lv_tabview |
LV_PART_MAIN | 标签页主体 |
| LV_PART_ITEMS | 顶部每个标签按钮 | |
lv_tileview |
LV_PART_MAIN | 平铺视图背景 |
| LV_PART_SCROLLBAR | 滑动指示条 | |
lv_win |
LV_PART_MAIN | 窗口主体 |
| LV_PART_SCROLLBAR | 滚动条 |
图像 / 动画类
| Widget | Part | 代表区域 |
|---|---|---|
lv_imagebutton |
LV_PART_MAIN | 图片按钮本体 |
lv_animimage |
LV_PART_MAIN | 动画图片本体 |
lv_gif |
LV_PART_MAIN | GIF 图片本体 |
lv_lottie |
LV_PART_MAIN | Lottie 矢量动画本体 |
lv_3dtexture |
LV_PART_MAIN | 3D 纹理本体 |
其他
| Widget | Part | 代表区域 |
|---|---|---|
lv_span |
LV_PART_MAIN | 富文本段落背景 |
lv_chart |
LV_PART_MAIN | 图表背景 |
| LV_PART_INDICATOR | 数据点 / 线 | |
| LV_PART_ITEMS | 网格线 / 刻度 | |
| LV_PART_CURSOR | 游标线 | |
| LV_PART_SCROLLBAR | 滚动条 | |
lv_calendar |
LV_PART_MAIN | 日历主体背景 |
| LV_PART_ITEMS | 每个日期单元格 | |
lv_spinner |
LV_PART_MAIN | 旋转动画的圆弧背景 |
| LV_PART_INDICATOR | 旋转的那段圆弧 | |
lv_arclabel |
LV_PART_MAIN | 圆弧背景 |
| LV_PART_INDICATOR | 沿圆弧排列的文字 |
通用说明
-
LV_PART_MAIN:所有 widget 都有,代表主体背景。 -
LV_PART_SCROLLBAR:任何可滚动控件都有,内容溢出时出现。 -
LV_PART_CUSTOM_FIRST:自定义 widget 的 part 扩展起点(从0x080000递增)。 -
LV_PART_ANY:通配符,仅在lv_obj_remove_style/lv_obj_replace_style中作过滤条件,不能用于lv_obj_set_style_*。 -
未列出的 part 表示该 widget 不支持,设置后无效(不报错但不显示)。
state(状态)
state通常与part连用,用于表示part改变的state。可以简单理解为if(state)则part(改变)。
state可以分为如下类别:
cpp
typedef enum {
LV_STATE_DEFAULT = 0, // 默认态,控件未被任何操作时的常态
LV_STATE_ALT = 1 << 0, // 替代态,用于同控件在不同上下文显示不同样式(如列表奇偶行)
/*1 reserved*/ // 第 1 位保留,未使用
LV_STATE_CHECKED = 1 << 2, // 选中态,开关打开 / 复选框打勾 / 单选选中
LV_STATE_FOCUSED = 1 << 3, // 聚焦态,通过触摸/鼠标点击获得焦点
LV_STATE_FOCUS_KEY = 1 << 4, // 键盘聚焦态,通过键盘/编码器导航获得焦点(与触摸聚焦区分)
LV_STATE_EDITED = 1 << 5, // 编辑态,控件正处于编辑模式(如 textarea 正在输入)
LV_STATE_HOVERED = 1 << 6, // 悬停态,鼠标指针停在控件上(需鼠标输入设备)
LV_STATE_PRESSED = 1 << 7, // 按下态,手指/鼠标正在按住控件
LV_STATE_SCROLLED = 1 << 8, // 滚动态,控件正在被滚动
LV_STATE_DISABLED = 1 << 9, // 禁用态,控件变灰不可交互
/*2 reserved*/ // 第 10~11 位保留,未使用
LV_STATE_USER_1 = 1 << 12, // 用户自定义状态 1
LV_STATE_USER_2 = 1 << 13, // 用户自定义状态 2
LV_STATE_USER_3 = 1 << 14, // 用户自定义状态 3
LV_STATE_USER_4 = 1 << 15, // 用户自定义状态 4
LV_STATE_ANY = 0xFFFF, // 通配符,仅在 remove/replace 样式时匹配所有状态
} lv_state_t;
可能看了解释还是看不懂state的含义。那这里具几个简单的例子,比如slider这个控件,我们可以设置其part的样式,但是这些还是不满足我的要求,我想让part的LV_PART_KNOB在我点击的时候变成其他颜色。此时就需要联动state了,设置LV_PART_KNOB,当状态满足state时,Part进行什么样的变化。
selector
正如刚才我们说的,state一般和part连用,两者共同组成selector。
看一下selector的原型:
cpp
/**
* A joint type for `lv_part_t` and `lv_state_t`. Example values
* - `0`: means `LV_PART_MAIN | LV_STATE_DEFAULT`
* - `LV_STATE_PRSSED`
* - `LV_PART_KNOB`
* - `LV_PART_KNOB | LV_STATE_PRESSED | LV_STATE_CHECKED`
*/
typedef uint32_t lv_style_selector_t;
平时使用,是将state | part使用,part占24位,state占16位。一共占24位。part只使用最高8位即可表示,剩余16位给state。所以,selector一种占24位。这个在结构体中也是可以看到的。
cpp
struct _lv_obj_style_t {
const lv_style_t * style;
uint32_t selector : 24;
uint32_t is_local : 1;
uint32_t is_trans : 1;
uint32_t is_disabled : 1;
uint32_t is_theme : 1; /**< The style is added by a theme */
};
注:如果一个selector只设置了state,那么part就是默认值为MAIN。可以state | state | state ......,表示为同时满足这多种场景。
三、API
lv_obj_set_style_bg_color
设置对象指定部分和状态下的背景颜色。
cpp
void lv_obj_set_style_bg_color(lv_obj_t * obj, lv_color_t value, lv_style_selector_t selector);
obj:要设置样式的 LVGL 对象
value:背景颜色
selector:指定样式作用在控件的哪个part和state。
可以看到,参数3就是selector,就是要指定样式的对象和状态。当然,这里可以不需要状态,只设置part可以。
lv_color_hex
根据 0xRRGGBB 十六进制值创建颜色
cpp
lv_color_t lv_color_hex(uint32_t c);
C:颜色值
lv_obj_set_style_bg_color的value参数,通常可以使用lv_color_hex来设置参数。那问题来了,我怎么知道要设置的颜色对应数值是多少?
这里可以借助Online Color Picker网站。该网站提供将颜色转化为十六进制数值的方法。

当然也可以通过hex,找到对应的颜色。

当我们需要非常精确的颜色的时候才需要用到lv_color_hex。如果我们只是需要一个大概的颜色,那就可以使用接下来的api。
lv_palette_main
获取指定调色板的颜色。
cpp
lv_color_t lv_palette_main(lv_palette_t p);
P:指定的调色板。类型为:lv_palette_t。那看一下lv_palette_t的具体值.
cpp
typedef enum {
LV_PALETTE_RED, // 红,警示/错误/删除
LV_PALETTE_PINK, // 粉,强调/品牌色
LV_PALETTE_PURPLE, // 紫,创意/高级感
LV_PALETTE_DEEP_PURPLE, // 深紫,沉稳强调
LV_PALETTE_INDIGO, // 靛青,商务/科技
LV_PALETTE_BLUE, // 蓝,主色/链接/信息
LV_PALETTE_LIGHT_BLUE, // 浅蓝,辅助/背景
LV_PALETTE_CYAN, // 青,提示/科技感
LV_PALETTE_TEAL, // 蓝绿,开关开启态常用
LV_PALETTE_GREEN, // 绿,成功/完成/选中高亮
LV_PALETTE_LIGHT_GREEN, // 浅绿,辅助
LV_PALETTE_LIME, // 柠檬绿,活泼点缀
LV_PALETTE_YELLOW, // 黄,警告/提醒
LV_PALETTE_AMBER, // 琥珀,暖警告
LV_PALETTE_ORANGE, // 橙,强调/旋钮
LV_PALETTE_DEEP_ORANGE, // 深橙,强强调/重要动作
LV_PALETTE_BROWN, // 棕,复古/大地色
LV_PALETTE_BLUE_GREY, // 蓝灰,中性背景/屏幕底色
LV_PALETTE_GREY, // 灰,背景/边框/禁用态
LV_PALETTE_LAST, // 边界标记,用于遍历循环(如 idx % LV_PALETTE_LAST)
LV_PALETTE_NONE = 0xff, // 无颜色,特殊占位
} lv_palette_t;
如果我们只需要大概的颜色,就可以通过这个结构体搭配lv_palette_main函数搞定。
lv_obj_set_style_pad_all
设置对象的四边内边距。
cpp
static inline void lv_obj_set_style_pad_all(lv_obj_t * obj, int32_t value, lv_style_selector_t selector)
obj:LVGL 对象
value:内边距的值,越大,留白越大。
selector:部件和状态,通常为0,默认值。
lv_obj_set_flex_flow
设置弹性布局容器内子对象的排列方向和换行规则。
cpp
void lv_obj_set_flex_flow(lv_obj_t * obj, lv_flex_flow_t flow);
obj:LVGL 对象
flow:排列方式。
lv_flex_flow_t的类型如下
cpp
typedef enum {
LV_FLEX_FLOW_ROW = 0x00, // 水平排列,从左到右(默认)
LV_FLEX_FLOW_COLUMN = LV_FLEX_COLUMN, // 垂直排列,从上到下
LV_FLEX_FLOW_ROW_WRAP = LV_FLEX_FLOW_ROW | LV_FLEX_WRAP, // 水平排列 + 放不下时换行
LV_FLEX_FLOW_ROW_REVERSE = LV_FLEX_FLOW_ROW | LV_FLEX_REVERSE, // 水平逆序,从右到左
LV_FLEX_FLOW_ROW_WRAP_REVERSE = LV_FLEX_FLOW_ROW | LV_FLEX_WRAP | LV_FLEX_REVERSE, // 水平 + 换行 + 逆序
LV_FLEX_FLOW_COLUMN_WRAP = LV_FLEX_FLOW_COLUMN | LV_FLEX_WRAP, // 垂直排列 + 放不下时换列
LV_FLEX_FLOW_COLUMN_REVERSE = LV_FLEX_FLOW_COLUMN | LV_FLEX_REVERSE, // 垂直逆序,从下到上
LV_FLEX_FLOW_COLUMN_WRAP_REVERSE = LV_FLEX_FLOW_COLUMN | LV_FLEX_WRAP | LV_FLEX_REVERSE, // 垂直 + 换列 + 逆序
} lv_flex_flow_t;
四、实例
看了这么多理论,来看一个实例。这个例子做个简单的part和state的例子。
cpp
void demo_parts(void)
{
lv_obj_t * scr = lv_screen_active();
lv_obj_set_style_pad_all(scr, 20, 0);
lv_obj_set_flex_flow(scr, LV_FLEX_FLOW_COLUMN); /* 子对象纵向自动排列 */
lv_obj_set_style_pad_row(scr, 12, 0); /* 行间距 12px */
/* 滑块:一个控件同时演示 part 和 state */
lv_obj_t * sld1 = lv_slider_create(scr); /* 在屏幕上创建滑块控件 */
lv_obj_set_width(sld1, lv_pct(100)); /* 宽度撑满父对象(屏幕宽度 - padding) */
lv_slider_set_value(sld1, 50, LV_ANIM_OFF); /* 设初始值 50(范围 0~100),无动画 */
/* 滑块:一个控件同时演示 part 和 state */
lv_obj_t * sld2 = lv_slider_create(scr); /* 在屏幕上创建滑块控件 */
lv_obj_set_width(sld2, lv_pct(100)); /* 宽度撑满父对象(屏幕宽度 - padding) */
lv_slider_set_value(sld2, 50, LV_ANIM_OFF); /* 设初始值 50(范围 0~100),无动画 */
/* ---- Part:同一控件的不同区域,分别配色 ---- */
lv_obj_set_style_bg_color(sld2, lv_palette_lighten(LV_PALETTE_GREY, 2), LV_PART_MAIN); /* 轨道灰 */
lv_obj_set_style_bg_color(sld2, lv_palette_main(LV_PALETTE_BLUE), LV_PART_INDICATOR); /* 填充蓝 */
lv_obj_set_style_bg_color(sld2, lv_color_hex(0xFF5722), LV_PART_KNOB); /* 旋钮橙 */
/* ---- State:旋钮在按下时变绿(part + state 组合)---- */
lv_obj_set_style_bg_color(sld2, lv_palette_main(LV_PALETTE_GREEN),
LV_PART_KNOB | LV_STATE_PRESSED); /* 按下绿 */
}
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);
demo_parts();
while(1) {
lv_timer_handler();
lv_delay_ms(5);
}
return 0;
}

这里我们创建了2个slider,第一个slider就是系统默认值。第二个slider我们设置其轨道part颜色为灰,设置其进度条part为蓝,设置旋钮part为橙,并且设置旋钮part在state为按下的时候变为绿色。
由这个demo就可以清晰的看到part和state的关系。