LVGL学习(六)- widget、part、state

一、简介

前面的介绍中,我们实现的页面都是比较简单的。当要实现一个复杂的页面的时候,只考虑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的关系。

相关推荐
t_guest13 天前
LVGL学习(四)- Objects
入门·lvgl
慕容白 MU14 天前
开源一个基于LVGL的图层管理框架(LMF)
开源·lvgl·图层管理框架
t_guest14 天前
LVGL学习(二)- 定时器
lvgl
t_guest14 天前
LVGL学习(三)- 日志
入门·lvgl
大熊猫侯佩1 个月前
WWDC26:SwiftUI 8 的 @State 全新“懒加载”机制与最佳实践
性能优化·swiftui·observable·懒加载·state·swift宏·实例初始化
zhbi982 个月前
LVGL8.3标签Label高级应用
ui·lvgl
The_superstar63 个月前
衡山派lvgl小练
串口·lvgl·衡山派·ds133ebs
The_superstar63 个月前
衡山派LVGL注意点
单片机·lvgl·衡山派·俊杰
xzl043 个月前
LVGL显示移植:lv_port_disp.c 详情介绍
rt-thread·lvgl·ra6m3-hmi-board