基于 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_animvoid 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 格式就行了,重新编译。
