没志青年
发布于 2025-09-10 / 47 阅读
0

03 LVGL 基础

基于 V9.4 版本

图层

LVGL具有图层概念,从顶层到底层依次是 sys_layer、top_layer、act_scr。

  • sys_layer:系统层,比如鼠标、光标、滚动条等始终可见的。不理解

  • top_layer:一些浮动控件,比如下拉菜单、提示框、弹窗。

  • act_scr:常用控件,用户绘制的界面。

LVGL 是可以实现多个屏幕显示的,但是同一时刻只有一个活跃的屏幕,lv_screen_active()用来获取当前的屏幕,这是所有控件的父类。

lv_obj 对象

lv_obj 对象可理解为 QT 中的 QObject 对象,lvgl 也像 QT 那样有父子对象机制。

lv_obj 对象的相关函数有固定的开头:lv_obj_set_<property_name>(widget, <value>)

lv_obj_pos.h 文件中。

默认情况下,新创建的控件会覆盖旧的控件。不过能修改,参考 Layers - LVGL 9.4 documentation

属性

位置

注意,位置是在父级的内容区域上的,不是全局的。

// 设置相对于父级的 X 轴位置
void lv_obj_set_x(lv_obj_t *obj, int32_t x)

// 设置相对于父级的 Y 轴位置
void lv_obj_set_y(lv_obj_t *obj, int32_t y)

// 设置相对于父级的 X、Y 轴位置
void lv_obj_set_pos(lv_obj_t *obj, int32_t x, int32_t y)

大小

// 设置宽
void lv_obj_set_width(lv_obj_t *obj, int32_t w)

// 设置高
void lv_obj_set_height(lv_obj_t *obj, int32_t h)

// 同时设置宽高
void lv_obj_set_size(lv_obj_t *obj, int32_t w, int32_t h)

对齐方式

设置了对齐,位置还有效吗?这两个一个会不会影响另一个,还是说最后的执行。待验证。

void lv_obj_set_align(lv_obj_t *obj, lv_align_t align)

Center是垂直和水平全居中,如果想要某一个方向居中,另一个方向有偏移,例如:

lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 40);    // 水平中心,垂直偏移40px

如果是 LV_ALIGN_BOTTOM_MID,偏移量要为负值

lv_align_t:

LV_ALIGN_DEFAULT
LV_ALIGN_TOP_LEFT
LV_ALIGN_TOP_MID
LV_ALIGN_TOP_RIGHT
LV_ALIGN_BOTTOM_LEFT
LV_ALIGN_BOTTOM_MID
LV_ALIGN_BOTTOM_RIGHT
LV_ALIGN_LEFT_MID
LV_ALIGN_RIGHT_MID
LV_ALIGN_CENTER
LV_ALIGN_OUT_TOP_LEFT
LV_ALIGN_OUT_TOP_MID
LV_ALIGN_OUT_TOP_RIGHT
LV_ALIGN_OUT_BOTTOM_LEFT
LV_ALIGN_OUT_BOTTOM_MID
LV_ALIGN_OUT_BOTTOM_RIGHT
LV_ALIGN_OUT_LEFT_TOP
LV_ALIGN_OUT_LEFT_MID
LV_ALIGN_OUT_LEFT_BOTTOM
LV_ALIGN_OUT_RIGHT_TOP
LV_ALIGN_OUT_RIGHT_MID
LV_ALIGN_OUT_RIGHT_BOTTOM

父子机制

(1)设置父对象

 lv_obj_set_parent(要操作的对象, 新的父对象)

(2)获取父对象

(3)获取子对象

事件

Events - LVGL 9.4 documentation

事件代码

自定义事件由用户定义和触发,LVGL 永远不会触发自定义事件。

给控件添加事件:

 lv_event_dsc_t *lv_obj_add_event_cb(lv_obj_t *obj, lv_event_cb_t event_cb, lv_event_code_t filter, void *user_data)

示例:

lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, user_data);

/* 事件处理函数 */
static void my_event_cb(lv_event_t * event)
{
printf(“Clicked\n”);
}

事件和回调函数是多对多的,一个回调函数可被不同控件,同一个控件的不同事件使用,。

控件支持的事件,在其详情页都有说明:

事件回调函数中的 lv_event_t 参数,一些处理方法:

  • lv_event_get_code(e):获取事件代码

  • lv_event_get_current_target(e):获取当前处理事件的对象

  • lv_event_get_target(e):获取第一个触发(第一个处理)该事件的对象

  • lv_event_get_target_obj(e) :获取事件最开始发生在哪个控件对象上,就是获取事件的源头。上面的返回值void *,上面的函数是不是多余了。

  • lv_event_get_user_data(e):设置事件回调函数时传入的参数

  • lv_event_get_param(e):手动发送事件时传入的参数

移除事件:

uint32_t i;
uint32_t event_cnt = lv_obj_get_event_count(widget); // 获取这个控件上添加的事件的总数
for(i = 0; i < event_cnt; i++) {
lv_event_dsc_t * event_dsc = lv_obj_get_event_dsc(widget, i);
if(lv_event_dsc_get_cb(event_dsc) == some_event_cb) {
lv_obj_remove_event(widget, i); // 逐次删除
break;
}
}

手动发送事件

比如,按钮关闭 Message Box

uint32_t btn_id = 0;
lv_obj_send_event(mbox, LV_EVENT_VALUE_CHANGED, &btn_id);

  • 对于Display显示设备,lv_display_send_event(widget, <EVENT_CODE>, &some_data)

  • 对于Input输入设备,lv_indev_send_event(widget, <EVENT_CODE>, &some_data)

事件冒泡机制

如果一个控件开启了 LV_OBJ_FLAG_EVENT_BUBBL,那么事件除了发给自己,还会继续上传(冒泡)给父对象。

如果父对象也设置了该标志,会再上传给爷爷对象,直到上级对象没有设置该标志。

事件下沉机制

Event Trickle

和冒泡机制反过来,是从上到下传递。

如果没有 LV_OBJ_FLAG_EVENT_TRICKLE,那他就不会向下传递,和冒泡一个思路。

手势检测

Gestures - LVGL 9.4 documentation

触发手势事件,必须满足两个条件:

  • 滑动时间:

  • 滑动距离:

样式

状态

LV_STATE_DEFAULT:(0x0000) 正常、释放的状态
LV_STATE_CHECKED:(0x0001) 切换或选中的状态
LV_STATE_FOCUSED:(0x0002) 通过键盘或编码器聚焦,或通过触摸板/鼠标点击
LV_STATE_FOCUS_KEY:(0x0004) 仅通过键盘或编码器聚焦,而非通过触摸板/鼠标
LV_STATE_EDITED:(0x0008) 由编码器编辑
LV_STATE_HOVERED:(0x0010) 鼠标悬停
LV_STATE_PRESSED:(0x0020) 被按下的状态
LV_STATE_SCROLLED:(0x0040) 正在滚动的状态
LV_STATE_DISABLED:(0x0080) 禁用的状态

// 用户自定义状态
LV_STATE_USER_1:(0x1000)
LV_STATE_USER_2:(0x2000)
LV_STATE_USER_3:(0x4000)
LV_STATE_USER_4:(0x8000)

状态可拼接,例如:LV_STATE_FOCUSED | LV_STATE_PRESSED

组成部分

LV_PART_MAIN:背景,主要区域
LV_PART_SCROLLBAR:滚动条
LV_PART_INDICATOR:指示器,例如用于滑块、条形图、开关或复选框的勾选框
LV_PART_KNOB:能调节值的部分

LV_PART_SELECTED:表示当前选择的选项或部分
LV_PART_ITEMS:如果小部件具有多个类似元素(例如表格单元格)时使用
LV_PART_CURSOR:标记特定位置,例如文本区域或图表的光标
LV_PART_CUSTOM_FIRST:可以从这里开始添加自定义部分标识符。

LV_PART_ANY:所有部分

一个控件由多个 part 组成,每个控件各有不同,例如,一个 Slider,有 LV_PART_MAIN、LV_PART_INDICATOR、LV_PART_KNOB

每个部分的样式各自独立。

样式可绑定到不同的状态或部分上。

LV_PART_MAIN | LV_STATE_DEFAULT 这种写法,可以简写为 0,因为这两个的值都是 0

样式初始化和设置

(1)初始化

样式必须是 static 修饰的、全局的、动态申请的一种,要让 LVGL 能在整个程序的生命周期能访问到,不然LVGL会运行崩溃。

最简单和常用的方法是 static

static lv_style_t style_btn;
lv_style_init(&style_btn);

(2)设置样式

样式设置函数命名规范:lv_style_set_<property_name>(&style, <value>);

与样式有关的函数太多了,放在了一个单独的页面:lvgl-api-desc

(3)移除样式

lv_style_remove_prop(&style, 样式类型,如 LV_STYLE_BG_COLOR);

(4)从样式中获取属性的值

lv_style_value_t v;
lv_result_t res = lv_style_get_prop(&style, LV_STYLE_BG_COLOR, &v);
if(res == LV_RESULT_OK) { /* Found /
do_something(v.color);
}

控件样式操作

Styles Overview - LVGL 9.4 documentation

LVGL 提供了lv_obj_set_style_ 开头的一系列函数,用于不通过lv_style_t ,直接设置控件的样式。参考 lvgl-api-desc

1、添加样式

lv_obj_add_style(btn, &style_btn, 0);                     / 设置正常状态下按钮的样式 /
lv_obj_add_style(btn, &btn_red, LV_STATE_PRESSED); /
设置按下时按钮的样式 */

2、移除样式

移除所有样式:

lv_obj_remove_style_all()

移除单个样式:

lv_obj_remove_style(widget, style, selector)

selector 为 part 和 state 的组合,如LV_PART_MAIN | LV_STATE_DEFAULT

两种情况:

1、style 和 selector 都不为 NULL:

(1)移除指定selector指定样式

(2)把某个样式从所有part或所有state中移除

lv_obj_remove_style(btn, &style_red, LV_PART_ANY);
lv_obj_remove_style(btn, &style_red, LV_STATE_ANY);
lv_obj_remove_style(btn, &style_red, LV_PART_ANY | LV_STATE_ANY); // 注意这里的 | 不能理解为 || ,这是 && 的意思。

2、style 为 NULL,selector 不为 NULL:

(1)移除指定部分或状态的所有样式

(2)去除控件的所有样式,相当于lv_obj_remove_style_all()

lv_obj_remove_style(btn, NULL, LV_PART_ANY | LV_STATE_ANY);

3、替换样式

bool lv_obj_replace_style(lv_obj_t *obj, const lv_style_t *old_style, const lv_style_t *new_style, lv_style_selector_t selector)

颜色

LVGL 8.x 中,LV_COLOR_PALETTE_BLUE 这种写法,但是在 9.x 版本中移除了。

9.x 内置颜色

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_color_t c = lv_palette_main(LV_PALETTE_xxx);
lv_palette_lighten
lv_palette_darken

使用16进制的颜色:

lv_color_t c = lv_color_hex(0x123456);

主题

Themes - LVGL 9.4 documentation

 lv_theme_t *lv_theme_default_init(
lv_display_t *disp,
lv_color_t color_primary,
lv_color_t color_secondary,
bool dark,
const lv_font_t *font)

主题是相同风格的样式的集合,修改全局的默认样式:

  lv_disp_t *display = lv_disp_get_default();
lv_theme_t theme =
lv_theme_default_init(display,
lv_palette_main(LV_PALETTE_BLUE), // 主颜色(蓝色)
lv_palette_main(LV_PALETTE_RED), // 次要颜色(红色)
true, // 使用深色模式
LV_FONT_DEFAULT // 默认字体
);
lv_display_set_theme(display, theme);

如果设置中开启了,那么会有默认的主题,上面的函数才可用。

#define LV_USE_THEME_DEFAULT 1

主题的相关函数

// 回调函数定义
typedef void (lv_theme_apply_cb_t)(lv_theme_t, lv_obj_t
)

// 函数
lv_theme_t *lv_theme_get_from_obj(lv_obj_t *obj)
void lv_theme_apply(lv_obj_t *obj)
void lv_theme_set_parent(lv_theme_t *new_theme, lv_theme_t *parent)
void lv_theme_set_apply_cb(lv_theme_t *theme, lv_theme_apply_cb_t apply_cb)
const lv_font_t *lv_theme_get_font_small(lv_obj_t *obj)
const lv_font_t *lv_theme_get_font_normal(lv_obj_t *obj)
const lv_font_t *lv_theme_get_font_large(lv_obj_t *obj)
lv_color_t lv_theme_get_color_primary(lv_obj_t *obj)
lv_color_t lv_theme_get_color_secondary(lv_obj_t *obj)

拓展主题

当不满足于 LVGL 内置的主题样式时,可拓展内置主题,在其基础上修改自己想要的样式,而不用完全重写主题。

拓展主题使用“主题链”机制实现,样式应用顺序:父主题 → 子主题 → 孙主题 …,后面主题的样式会覆盖前面主题的样式,从而达到“修改”的效果。

因此,要把内置主题当做父主题,自定义主题当做子主题。

示例代码:(AI写的,未验证,特别是回调函数这块)

查询控件的类型:Search const lv_obj_class_t - LVGL 9.4

// 自定义主题回调函数
static void my_theme_apply_cb(lv_theme_t * th, lv_obj_t * obj)
{
if(lv_obj_check_type(obj, &lv_button_class)) {
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_palette_main(LV_PALETTE_GREEN));
lv_obj_add_style(obj, &style_btn, 0);
}
}

// 自定义主题初始化
// 官方的是lv_theme_default_init,我们也实现个自己的初始化主题函数
lv_theme_t * my_theme_init(lv_display_t * disp, lv_theme_t * parent)
{
static lv_theme_t theme;
lv_theme_set_apply_cb(&theme, my_theme_apply_cb); // 设置回调函数
lv_theme_set_parent(&theme, parent); // 继承内置主题
theme.disp = disp;
return &theme;
}

// 内置主题初始化
lv_theme_t * base = lv_theme_default_init(disp, lv_palette_main(LV_PALETTE_BLUE),
lv_palette_main(LV_PALETTE_RED),
LV_THEME_DEFAULT_LIGHT, &lv_font_default());
// 自定义主题初始化
lv_theme_t * ext = my_theme_init(disp, base);

lv_display_set_theme(disp, ext); // 应用扩展后的主题

页面切换

lv_scr_load(ui_HomePage);

#define lv_scr_load                      lv_screen_load
#define lv_scr_load_anim lv_screen_load_anim

void lv_screen_load(struct _lv_obj_t *scr) {
lv_screen_load_anim(scr, LV_SCR_LOAD_ANIM_NONE, 0, 0, false);
}

void lv_screen_load_anim(lv_obj_t *new_scr, lv_screen_load_anim_t anim_type,
uint32_t time, uint32_t delay, bool auto_del);

页面切换动画

LVGL 启动流程

Learn the Basics - LVGL 9.4 documentation

(1)硬件初始化(LCD屏幕、触摸、SPI控制器等硬件)

(2)LVGL 初始化,调用函数:lv_init();

(3)LVGL动画、交互处理等都需要知道时间(ms)

有两种方式:

1、时间由外部维护,设置回调函数,让 LVGL 能获取时间。

RTOS、HAL 用这个。

 void lv_tick_set_cb(lv_tick_get_cb_t cb)

2、时间由 LVGL 维护,需要周期性的调用。

 void lv_tick_inc(uint32_t tick_period)

(4)显示设置、缓冲区域设置,这些在移植文件中,调用对应函数既可。

(5)到这里LVGL就初始化好了,可以写界面了。

(6)周期性调用 lvgl 事件处理函数,间隔 5 ms

while(1) {
lv_timer_handler();
my_sleep(5);
}

  • 裸机在 while(1){} 中调用

  • Linux 在一个线程中调用

  • RTOS 在一个任务中调用。不推荐在软件定时器中调用,因为不能执行耗时操作,而且优先级比较低,不能保证及时调用,导致响应卡顿。也不要在时基回调函数中调用,会降低系统整体响应速度。

Keil 编译 lvgl 报错

这是编码格式的问题。

解决:

使用 vscode 打开项目,打开报错的文件:

改为 utf8bom 格式就行了,重新编译。