LVGL介绍
配置要求
基本上,每个能够驱动显示器的现代控制器都适合运行 LVGL。 最低要求是:
16、32 或 64 位微控制器或处理器
建议使用 >16 MHz 时钟速度
闪存/ROM: > 64 kB 用于非常重要的组件 (> 建议使用 180 kB)
RAM:静态 RAM 使用量:~2 kB,取决于使用的功能和对象类型堆: > 2kB (> 建议使用 8 kB)动态数据(堆): > 2 KB (> 如果使用多个对象,建议使用 16 kB). 在 lv_conf.h 文件中配置 LV_MEM_SIZE 生效。显示缓冲区:> “水平分辨率”像素(推荐 >10 × 10ד 水平分辨率”)MCU或外部显示控制器中的一个帧缓冲区
C99 或更新的编译器
快速概览
基础知识
Widgets(控件)
按钮、标签、滑块、图表等图形元素称为对象或小控件。
控件有如下:
Table(表格) (lv_table) — LVGL 文档
Base object(基础对象) (lv_obj)
Arc(圆弧 ) (lv_arc)
Animation Image (动画图像) (lv_animimg)
Bar (进度条)(lv_bar)
Button(按钮) (lv_button)
Button matrix(矩阵按钮) (lv_buttonmatrix)
Calendar(日历) (lv_calendar)
Chart(图表) (lv_chart)
Canvas(画布)(lv_canvas)
Checkbox(复选框) (lv_checkbox)
Drop-down list(下拉列表)(lv_dropdown)
Image (图象)(lv_image)
Image button (图像按钮)(lv_imagebutton)
Keyboard(键盘) (lv_keyboard)
Label (标签)(lv_label)
LED(指示灯) (lv_led)
Line (线条)(lv_line)
List(列表) (lv_list)
Lottie (lv_lottie)
Menu (菜单)(lv_menu)
Message box(消息框) (lv_msgbox)
Roller (滚轮)(lv_roller)
Scale (标尺)(lv_scale)
Slider (滑动条)(lv_slider)
Span(富文本) (lv_span)
Spinbox (微调框)(lv_spinbox)
Spinner(环形加载器) (lv_spinner)
Switch (开关)(lv_switch)
Table(表格) (lv_table)
Tabview(选项卡) (lv_tabview)
Text area(文本框) (lv_textarea)
Tile view(平铺视图) (lv_tileview)
Window (窗口)(lv_win)
每个对象都有一个创建它的父对象。例如,如果在按钮上创建标签,则该按钮是标签的父级。
子对象与父对象一起移动,如果删除父对象,子对象也将被删除。
子项只能在其父项上可见。换句话说,父级之外的子级部分被剪掉了。
Screen 是“根”父级。您可以拥有任意数量的屏幕。
要获取当前屏幕调用 lv_screen_active(),并使用 lv_screen_load(scr1) 加载屏幕。
使用 lv_<type>_create(parent) 创建一个新对象。它将返回一个 lv_obj_t * 变量,该变量可用作对象的引用以设置其参数。
例:
// 创建一个slider对象/控件
lv_obj_t * slider1 = lv_slider_create(lv_screen_active());
// 设置属性
lv_obj_set_x(slider1, 30);
lv_obj_set_y(slider1, 10);
lv_obj_set_size(slider1, 200, 50);
1
2
3
4
5
6
除了基本属性外,小部件还可以具有特定于类型的小部件由 lv_<widget_type>_set_<parameter_name>(obj, <value>) 函数设置的参数。例如:
lv_slider_set_value(slider1, 70, LV_ANIM_ON);
1
Events(事件)
事件用于通知用户某个对象发生了某些事情。 您可以将一个或多个回调分配给一个对象,如果该对象被单击、释放、拖动、删除等将被调用。
// 为按钮对象添加一个点击事件回调函数
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL); /*Assign a callback to the button*/
void btn_event_cb(lv_event_t * e)
{
printf("Clicked\n");
}
1
2
3
4
5
6
从 lv_event_t * e 中,可以使用以下命令检索当前事件代码:
lv_event_code_t code = lv_event_get_code(e);
1
可以使用以下命令检索触发事件的对象:
lv_obj_t * obj = lv_event_get_target(e);
1
Parts(部分)
控件Widgets可能由一个或多个部分组成。
例如,一个按钮只有一个名为 LV_PART_MAIN 的部分。但是, lv_slider(滑块) 具有 LV_PART_MAIN、 LV_PART_INDICATOR 和 LV_PART_KNOB。
States(状态)
LVGL 对象可以处于以下状态的组合:
LV_STATE_DEFAULT: 正常,释放状态
LV_STATE_CHECKED: 切换或选中状态
LV_STATE_FOCUSED: 通过键盘或编码器聚焦或通过触摸板/鼠标点击
LV_STATE_FOCUS_KEY: 通过键盘或编码器聚焦,但不通过触摸板/鼠标聚焦
LV_STATE_EDITED: 由编码器编辑
LV_STATE_HOVERED: 鼠标悬停(现在不支持)
LV_STATE_PRESSED: 被按下
LV_STATE_SCROLLED: 正在滚动
LV_STATE_DISABLED: 禁用
例如,如果你按下一个对象,它会自动进入 LV_STATE_FOCUSED 和 LV_STATE_PRESSED 状态,当你释放它时,LV_STATE_PRESSED 状态将被移除,保持活动状态。
要检查对象是否处于给定状态,请使用 lv_obj_has_state(obj, LV_STATE_...)。如果对象当时处于该状态,它将返回 true 。
要手动添加或删除状态,请使用下面的函数:
lv_obj_add_state(obj, LV_STATE_...);
lv_obj_remove_state(obj, LV_STATE_...);
1
2
Styles(样式)
一个样式实例包含诸如背景颜色、边框宽度、字体等属性,用来描述对象的外观。
样式用 lv_style_t 变量表示。在对象中只保存它们的指针,因此需要将其定义为静态或全局变量。在使用样式之前,需要使用 lv_style_init(&style1) 进行初始化。然后可以添加属性来配置样式。
static lv_style_t style1;
lv_style_init(&style1);
lv_style_set_bg_color(&style1, lv_color_hex(0xa03080))
lv_style_set_border_width(&style1, 2))
1
2
3
4
使用对象的部分Part和状态State的ORed组合来分配样式。例如,当滑块被按下时,在滑块指示器上使用这种样式:
// 在按下状态和INDICATOR部分时应用该样式
lv_obj_add_style(slider1, &style1, LV_PART_INDICATOR | LV_STATE_PRESSED);
1
2
如果部分Part是LV_PART_MAIN,则可以省略它。
lv_obj_add_style(btn1, &style1, LV_STATE_PRESSED); /*Equal to LV_PART_MAIN | LV_STATE_PRESSED*/
1
如果状态State是LV_STATE_DEFAULT,也可以省略。
lv_obj_add_style(btn1, &style1, 0); /*Equal to LV_PART_MAIN | LV_STATE_DEFAULT*/
1
样式可以级联(类似于CSS)。这意味着您可以向对象的一部分添加更多样式。例如,style_btn 可以设置默认按钮外观,而 style_btn_red 可以覆盖背景颜色,使按钮变为红色:
lv_obj_add_style(btn1, &style_btn, 0);
lv_obj_add_style(btn1, &style1_btn_red, 0);
1
2
如果当前状态下没有设置属性,则将使用带有LV_STATE_DEFAULT的样式。如果在默认状态中未定义属性,则会使用默认值。
某些属性(通常是与文本相关的属性)可以被继承。这意味着如果对象中未设置某个属性,系统会在其父级中查找该属性。例如,您可以在屏幕样式中设置一次字体,然后该屏幕上的所有文本都将默认继承它。
还可以向对象添加局部样式属性。这将创建一个仅由对象内部使用的样式:
// 为slider控件设置背景颜色
lv_obj_set_style_bg_color(slider1, lv_color_hex(0x2080bb), LV_PART_INDICATOR | LV_STATE_PRESSED);
1
2
Themes(主题)
主题是对象的默认样式。创建对象时,将自动应用来自主题的样式。
应用程序的主题是在 lv_conf.h 中设置的编译时配置。
概述
Objects(对象)
在LVGL中,用户界面的 基本组成部分 是对象(控件),也称为 Widgets。例如,一个 按钮、标签、图像、列表、图表 或者 文本区域。
所有的对象都使用 lv_obj_t 指针作为句柄进行引用。之后可以使用该指针来设置或获取对象的属性。
属性
基本属性
所有的对象类型都有一些通用的基本属性:
位置
大小
父级
样式
事件处理程序
等等
可以使用 lv_obj_set_... 和 lv_obj_get_... 函数设置或者获取这些属性。
/*设置基本对象属性*/
lv_obj_set_size(btn1, 100, 50); /*设置按钮的大小*/
lv_obj_set_pos(btn1, 20,30); /*设置按钮的位置*/
1
2
3
特殊属性
对象类型也有特殊的属性。例如,滑块有:
最小值和最大值
当前值
针对这些特殊属性,每个对象类型可能有独特的API函数。例如,对于滑块:
/*设置滑块特定属性*/
lv_slider_set_range(slider1, 0, 100); /*设置最小值和最大值*/
lv_slider_set_value(slider1, 40, LV_ANIM_ON); /*设置当前值(位置)*/
1
2
3
所有控件的API在它们各自的 文档 中有描述,也可以查看头文件。
工作机制
父子结构
一个父对象可以被视为其子对象的容器。每个对象都必须会有且仅有一个父对象(屏幕除外),但一个父对象可以有任意数量的子对象。父对象的类型没有限制,但是有些对象一般是父对象(例如按钮)或者是子对象(例如标签)。
一起移动
子对象的位置随父对象的位置改变。所有子对象的位置都是相对于父对象。
// 父子结构
lv_obj_t *parent = lv_obj_create(lv_screen_active());
lv_obj_set_size(parent, 300, 240);
lv_obj_t *obj1 = lv_obj_create(parent);
lv_obj_set_pos(obj1, 10, 10);
lv_obj_set_size(obj1, 100, 100);
lv_obj_set_pos(parent, 40, 40);
1
2
3
4
5
6
7
仅在父对象上可见
lv_obj_set_x(obj1, -30); /*将子对象移出父对象一点点*/
1
可以通过这个方法覆盖此行为 lv_obj_add_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE),这会允许子对象在父对象之外进行绘制。
创建和删除对象
在LVGL中,可以在运行时动态创建或删除对象。这也就是说,只有当对象被创建之后才会消耗内存资源。
因此,您可以在点击按钮准备打开新界面(屏幕)时再创建新界面(屏幕),并在加载新界面(屏幕)时删除旧界面(屏幕)。
UI可根据设备的当前环境进行创建。例如,可以根据当前连接的传感器创建仪表、图表、条形图和滑块。
每个控件都有自己的 create 函数,函数原型如下:
lv_obj_t * lv_<widget>_create(lv_obj_t * parent, <如果有其他参数>);
1
通常,创建函数只有一个 parent 参数,指示在哪个对象上创建该控件。
返回值是指向创建出来的控件的指针,类型为 lv_obj_t *。
删掉对象适用 lv_obj_delete 函数:
void lv_obj_delete(lv_obj_t *obj);
1
lv_obj_del() 会立即删除对象。如果出于任何原因无法立即删除对象,可以使用 lv_obj_delete_async ,它会在下一次调用 lv_timer_handler() 时执行删除操作。
可以使用 lv_obj_clean(obj) 删除对象的所有子对象(但不包括对象本身)。
可以使用 lv_obj_delete_delayed(obj, 1000) 在经过一段时间后再删除对象,以毫秒为单位。
屏幕
创建屏幕
屏幕是一种特殊的对象,它们没有父对象。因此可以像这样创建屏幕:
lv_obj_t * scr1 = lv_obj_create(NULL);
1
获取活动屏幕
每个显示器上都会存在一个活动屏幕。默认情况下,库会为每个显示器创建和加载一个名为“Base object”的屏幕。
要获得当前活动的屏幕,请使用 lv_screen_active() 函数。
加载屏幕
使用 lv_screen_load(lv_obj_t *scr) 来加载新的屏幕。
Layers(层)
使用 lv_screen_load(lv_obj_t *scr) 来加载新的屏幕。 自动生成两个图层:
顶层(top layer)
系统层(system layer)
它们与屏幕独立,将显示在每个屏幕上。 顶层位于屏幕上每个对象之上, 系统层 位于 顶层 之上。您可以自由地向 顶层 添加任何弹窗。但是, 系统层 受到系统级别的限制(例如,鼠标光标将与 lv_indev_set_cursor() 一起放置在那里)。
层级: 活动屏幕(screen_active) < 顶层(top layer) < 系统层(system layer)
lv_layer_top() 和 lv_layer_sys() 函数返回指向顶层和系统层的指针。
用动画加载屏幕
可以使用 lv_screen_load_anim(scr, transition_type, time, delay, auto_del) 来加载一个带动画效果的新屏幕。可以设置以下动画过渡类型:
LV_SCR_LOAD_ANIM_NONE: 在 delay 毫秒后立即切换
LV_SCR_LOAD_ANIM_OVER_LEFT, LV_SCR_LOAD_ANIM_OVER_RIGHT, LV_SCR_LOAD_ANIM_OVER_TOP 和 LV_SCR_LOAD_ANIM_OVER_BOTTOM:将新屏幕沿着给定方向移动到当前屏幕上方
LV_SCR_LOAD_ANIM_OUT_LEFT, LV_SCR_LOAD_ANIM_OUT_RIGHT, LV_SCR_LOAD_ANIM_OUT_TOP 和 LV_SCR_LOAD_ANIM_OUT_BOTTOM:将旧屏幕沿着给定方向移出到当前屏幕外
LV_SCR_LOAD_ANIM_MOVE_LEFT, LV_SCR_LOAD_ANIM_MOVE_RIGHT, LV_SCR_LOAD_ANIM_MOVE_TOP 和 LV_SCR_LOAD_ANIM_MOVE_BOTTOM:将当前屏幕和新屏幕都沿着给定方向移动
LV_SCR_LOAD_ANIM_FADE_IN 和 LV_SCR_LOAD_ANIM_FADE_OUT:将新屏幕渐变到旧屏幕上,或反之亦然
将 auto_del 设置为 true 将在动画完成后自动删除旧屏幕。
新屏幕将在 delay 时间后动画开始时变为活动状态(由 lv_screen_active() 返回)。在屏幕动画期间,所有输入都将被禁用。
处理多个显示器
屏幕是在当前选定的 默认显示器 上创建的。 默认显示器 是最后使用 lv_display_create() 注册的显示器。还可以使用 lv_display_set_default(disp) 显式地选择新的默认显示器。
lv_screen_active() ,lv_screen_load() 和 lv_screen_load_anim() 操作默认显示器。
Parts(部分)
小部件Widget由多个控件组成。例如,一个 Base object 有主要部分和滚动条部分,而一个 Slider 有主要部分、指示器部分和旋钮部分。部件类似于CSS中的 伪元素。
LVGL中存在以下预定义的部分:
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:可以从这里添加自定义部分。
小部件Widgets的主要目的是允许对控件的 “组件(组成部分)” 进行样式设置。
States(状态)
控件可以处于以下状态的组合:
LV_STATE_DEFAULT:正常释放状态
LV_STATE_CHECKED:切换或选中状态
LV_STATE_FOCUSED:通过键盘或编码器聚焦或通过触摸板/鼠标点击
LV_STATE_FOCUS_KEY:通过键盘或编码器聚焦但不通过触摸板/鼠标点击
LV_STATE_EDITED:由编码器编辑
LV_STATE_HOVERED:由鼠标悬停(目前不支持)
LV_STATE_PRESSED:正在按下
LV_STATE_SCROLLED:正在滚动
LV_STATE_DISABLED:禁用状态
LV_STATE_USER_1:自定义状态
LV_STATE_USER_2:自定义状态
LV_STATE_USER_3:自定义状态
LV_STATE_USER_4:自定义状态
通常情况下,LVGL库会根据用户与对象的交互(按下、释放、聚焦等)自动更改状态。然而,状态也可以用户手动进行更改。 要设置或清除给定的状态(但保持其他状态不变),可以使用 lv_obj_add/remove_state(obj, LV_STATE_...)。
在这两种情况下,也可以使用按位或运算符来组合状态值。例如: lv_obj_add_state(obj, part, LV_STATE_PRESSED | LV_PRESSED_CHECKED)。
Snapshot(快照)
一个对象和其子对象的快照图像可以一起生成。
Positions,sizes,and layouts(位置、大小和布局)
LVGL的坐标设置的概念受到CSS的启发,LVGL并不完全实现了CSS,但实现了一个类似的子集(有的地方进行了微小调整)。
显式设置的坐标存储在样式中(大小、位置、布局等)
支持最小宽度、最大宽度、最小高度、最大高度
有像素、百分比和“内容(content)”单位
x=0;y=0坐标表示父对象的 左上角 + 左或上内边距 + 边框的宽度
宽/高(width/height)表示完整的尺寸,“内容区域”较小,带有填充和边框宽度
支持flexbox和grid布局的部分功能(子集)
单位
像素(pixel):简单地说就是一个以像素为单位的位置。整数总是指像素。
例如 lv_obj_set_x(btn, 10) (设置按钮的横(x)坐标为10个像素)
百分比(percentage):对象或其父对象大小的百分比。 lv_pct(value) 将一个值转换为百分比。
例如 lv_obj_set_width(btn, lv_pct(50)) (将按钮的宽度设置为父级宽度的50%)
LV_SIZE_CONTENT:设置对象宽度/高度的特殊值,将会根据子对象所需的大小自动调整自身大小。类似于CSS中的 auto。
例如 : lv_obj_set_width(btn, LV_SIZE_CONTENT)(将按钮的宽度设置为自适应内容宽度)
盒子模型
LVGL遵循CSS的 border-box 模型。一个对象的“盒子”由以下部分构成:
边界框:元素的宽度/高度。
边框宽度:边框的宽度。
填充:对象与其子元素之间的间距。
外边距:对象外部的间距(仅由某些布局考虑)
内容:内容区域,即边界框减去边框宽度和内边距的大小。
Border属于边界框内,Outline属于边界框外。
重要笔记
本节描述LVGL的行为可能会出现出乎意料的特殊情况。
坐标会被延迟计算
LVGL不会立即重新计算所有坐标变化,这样做是为了提高性能。相反,对象会被标记为"脏(dirty)",在重新绘制屏幕之前,LVGL会检查是否有任何"dirty"对象。如果有,则会刷新它们的位置、大小和布局。
换句话说,如果您需要获取对象的坐标,并且坐标刚刚发生了变化,LVGL需要强制重新计算坐标。要做到这一点,请调用 lv_obj_update_layout()。
大小和位置可能取决于父级或布局。因此,lv_obj_update_layout() 会重新计算 obj 屏幕上所有对象的坐标。
删除样式
坐标有两种设置方式,一种是通过style对象设置,它会存储在style对象内部(RAM)中,另外一种是使用lv_obj_set_xxx,它会将数据保存在该对象的本地样式中。
这是一个内部机制,在你使用LVGL时并不太需要重点关注。 然而,有一个情况下你需要注意实现方式。 如果通过以下方式移除对象的样式:
lv_obj_remove_style_all(obj)
1
或者
lv_obj_remove_style(obj, NULL, LV_PART_MAIN);
1
这会导致之前设置的坐标也将被移除。
例如:
/* obj1 的大小将在最后被设置回默认值 */
lv_obj_set_size(obj1, 200, 100); /* 现在 obj1 的大小为 200;100 */
lv_obj_remove_style_all(obj1); /* 它会移除设置的大小 */
/* obj2 最后将会有 200;100 的大小 */
lv_obj_remove_style_all(obj2);
lv_obj_set_size(obj2, 200, 100);
1
2
3
4
5
6
Position(位置)
最简单的方法
如果想最简单地设置对象的x和y坐标,可以这样操作:
lv_obj_set_x(obj, 10); //单独设置...
lv_obj_set_y(obj, 20);
lv_obj_set_pos(obj, 10, 20); //或者使用一个函数
1
2
3
默认情况下,x和y坐标是从父元素的内容区域的左上角开始计算的。例如,如果父元素的每一边都有五个像素的填充(padding),那么上面的代码会把 obj 放置在(15, 25),因为内容区域在填充之后开始计算。
百分比值是通过父元素的内容(content)区域的大小来计算的。
lv_obj_set_x(btn, lv_pct(10)); //x = 父元素内容区域宽度的10%
1
对齐
在某些情况下,可以方便地从对象默认的左上角更改其定位原点。如果改变了原点,比如改成底部-右侧,那么(0,0)位置的意思是:与底部-右侧对齐。要改变原点,使用如下代码:
lv_obj_set_align(obj, align);
1
改变对齐方式并设置新的坐标:
lv_obj_align(obj, align, x, y);
1
有以下对齐选项可用:
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_obj_center(obj);
//有相同的效果
lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);
1
2
3
如果父对象的大小改变,则子对象的设置对齐和位置会根据父对象的变化自动调整更新。
上述介绍的功能使对象对齐到其父对象。然而,也可以将对象对齐到任意参考对象。
lv_obj_align_to(obj_to_align, reference_obj, align, x, y);
1
除了上述的对齐选项外,还可以使用以下选项将对象对齐到参考对象外部:
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
例如,将标签对齐到按钮上方并使标签水平居中:
lv_obj_align_to(label, btn, LV_ALIGN_OUT_TOP_MID, 0, -10);
1
注意,lv_obj_align_to()不能在对象的坐标或参考对象的坐标发生变化时重新对齐对象。
Size(大小)
最简单的方法
一个对象的宽度和高度也可以很容易地进行设置:
lv_obj_set_width(obj, 200); //分别设置...
lv_obj_set_height(obj, 100);
lv_obj_set_size(obj, 200, 100); //或者使用一个函数
1
2
3
百分比值是基于父对象的内容区域大小进行计算的。例如,要将对象的高度设置为屏幕高度:
lv_obj_set_height(obj, lv_pct(100));
1
大小设置支持特殊值:LV_SIZE_CONTENT。这意味着对象在相应方向上的大小将被设置为其子对象恰好所需的大小。请注意,只有右侧和底部的子对象才会被考虑,而顶部和左侧的子对象仍会被裁剪。这种限制使行为更可预测。
具有 LV_OBJ_FLAG_HIDDEN 或 LV_OBJ_FLAG_FLOATING 的对象将被 LV_SIZE_CONTENT 计算忽略。
上述函数设置对象边界框的大小,但内容区域的大小也可以设置。这也就是说,对象的边界框会根据填充的大小进行调整。
lv_obj_set_content_width(obj, 50); //实际宽度:左填充 + 50 + 右填充
lv_obj_set_content_height(obj, 30); //实际高度:顶部填充 + 30 + 底部填充
1
2
可以使用以下函数获取边界框和内容区域的大小:
int32_t w = lv_obj_get_width(obj);
int32_t h = lv_obj_get_height(obj);
int32_t content_w = lv_obj_get_content_width(obj);
int32_t content_h = lv_obj_get_content_height(obj);
1
2
3
4
使用样式
在底层实现中,位置、大小和对齐属性是样式属性。上述描述的“简单函数”隐藏了与样式相关的代码来简化操作,实质上已经在对象的本地样式中设置位置、大小和对齐等属性。
然而,使用样式来设置坐标具有一些重要的优点:
使得同时设置多个对象的宽度/高度等变得简单。比如,使所有滑块的尺寸为 100x10 像素。
还可以在一个位置修改值。
这些数值可以部分地被其他样式覆盖。例如, style_btn 默认将对象的宽度设置为 100x50,但添加 style_full_width 仅覆盖对象的宽度。
对象的位置或大小可以根据状态而有所不同。例如,在 LV_STATE_DEFAULT 状态下宽度为100像素,在 LV_STATE_PRESSED 状态下为120像素。
可以使用样式转换使坐标变化更加平滑。
以下是使用样式设置对象大小的一些示例代码:
static lv_style_t style;
lv_style_init(&style);
lv_style_set_width(&style, 100);
lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_add_style(btn, &style, LV_PART_MAIN);
1
2
3
4
5
为了保持 LVGL API 的精简,只有最常见的坐标设置功能有一个“简单”版本,更复杂的功能可以通过样式来实现。
Translation(位置转换)
现在假设有3个相邻的按钮。它们的位置如上所述。现在你想要在按下按钮时将按钮上移一点。
实现这一目标的一种方法是为其按下状态设置一个新的Y坐标:
// 这种方法有效,但不够灵活,因为按下时的坐标是硬编码的。如果按钮不在y=100处, style_pressed 就不会如预期般工作。
static lv_style_t style_normal;
lv_style_init(&style_normal);
lv_style_set_y(&style_normal, 100);
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_y(&style_pressed, 80);
lv_obj_add_style(btn1, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn1, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn2, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn2, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn3, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn3, &style_pressed, LV_STATE_PRESSED);
// 可以使用平移来解决这个问题
static lv_style_t style_normal;
lv_style_init(&style_normal);
lv_style_set_y(&style_normal, 100);
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
// 平移
lv_style_set_translate_y(&style_pressed, -20);
lv_obj_add_style(btn1, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn1, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn2, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn2, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn3, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn3, &style_pressed, LV_STATE_PRESSED);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
平移是相对于对象当前位置进行应用的。
在平移中也可以使用百分比值。百分比是相对于对象的大小(而不是相对于父对象的大小)。例如 lv_pct(50) 将使物体移动一半的宽度/高度。
在布局计算之后应用平移。因此,即使对象的位置总是被布局计算,也可以进行平移。
平移实际上移动了对象。这意味着它会使滚动条和 LV_SIZE_CONTENT 大小的对象对位置变化做出反应。
Transformation(大小转换)
对象的大小可以相对于当前大小进行改变。变换后的宽度和高度会分别加在对象的两侧。这意味着,一个变换后的宽度为10px会使对象的宽度增加20个像素。
与位置转换不同,大小转换并不会使对象“实际”变大。换句话说,滚动条、布局和 LV_SIZE_CONTENT 不会对变换后的大小做出反应。因此,大小转换 “只是” 一种视觉效果。
下面的代码会在按钮被按下时放大:
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_transform_width(&style_pressed, 10);
lv_style_set_transform_height(&style_pressed, 10);
lv_obj_add_style(btn, &style_pressed, LV_STATE_PRESSED);
1
2
3
4
5
Min and Max size(最小和最大尺寸)
与CSS类似,LVGL也支持 min-width、 max-width、 min-height和 max-height。这些限制了对象的大小,防止其变得比这些值更小/更大。如果通过百分比或 LV_SIZE_CONTENT 来设置大小,它们尤其有用。
static lv_style_t style_max_height;
lv_style_init(&style_max_height);
lv_style_set_y(&style_max_height, 200);
lv_obj_set_height(obj, lv_pct(100));
lv_obj_add_style(obj, &style_max_height, LV_STATE_DEFAULT); //将高度限制为200像素
1
2
3
4
5
也可以使用百分比值,相对于父容器的内容区域的大小。
static lv_style_t style_max_height;
lv_style_init(&style_max_height);
lv_style_set_y(&style_max_height, lv_pct(50));
lv_obj_set_height(obj, lv_pct(100));
lv_obj_add_style(obj, &style_max_height, LV_STATE_DEFAULT); //将高度限制为父容器高度的一半
1
2
3
4
5
Layout(布局)
概述
布局可以更新对象子元素的位置和大小。它们可以用于自动排列子元素成一行或一列,或者以更复杂的形式排列。
布局设置的位置和大小会覆盖“正常”的x、y、宽度和高度设置。
每个布局都有一个相同的函数: lv_obj_set_layout(obj, <布局名称>) ,用于在对象上设置布局。
内置布局
LVGL带有两种非常强大的布局:
Flexbox:将对象排列成行或列,支持换行和扩展项目。
Grid:在二维表中将对象排列成固定位置。
Flags(标志)
有一些标志可以用于对象,以影响它们与布局的行为:
LV_OBJ_FLAG_HIDDEN 隐藏的对象在布局计算中被忽略。
LV_OBJ_FLAG_IGNORE_LAYOUT 该对象被布局简单地忽略。它的坐标可以像常规那样设置。
LV_OBJ_FLAG_FLOATING 与 LV_OBJ_FLAG_IGNORE_LAYOUT 相同,但具有 LV_OBJ_FLAG_FLOATING 标志的对象将在 LV_SIZE_CONTENT 计算中被忽略。
这些标志可以使用 lv_obj_add_flag(obj, FLAG) 和 lv_obj_remove_flag(obj, FLAG) 添加/移除。
添加新布局
LVGL可以通过自定义布局自由扩展,如下所示:
uint32_t MY_LAYOUT;
...
MY_LAYOUT = lv_layout_register(my_layout_update, &user_data);
...
void my_layout_update(lv_obj_t * obj, void * user_data)
{
/*如果需要重新定位/调整“obj”的子对象,则会自动调用该函数*/
}
1
2
3
4
5
6
7
8
可以添加自定义样式属性,并在更新回调函数中检索和使用它们。例如:
uint32_t MY_PROP;
...
LV_STYLE_MY_PROP = lv_style_register_prop();
...
static inline void lv_style_set_my_prop(lv_style_t * style, uint32_t value)
{
lv_style_value_t v = {
.num = (int32_t)value
};
lv_style_set_prop(style, LV_STYLE_MY_PROP, v);
}
1
2
3
4
5
6
7
8
9
10
11
Styles(样式)
“Styles” 用于设置对象的外观。lvgl中的样式受到CSS的启发。概念如下:
样式是一个 lv_style_t 变量,它可以保存诸如边框宽度、文字颜色等属性。类似于CSS中的 class。
样式可以分配给对象以改变它们的外观。在分配时,可以指定目标部分(CSS中的伪元素)和目标状态(伪类)。例如,当滑块处于按下状态时,可以为其添加 style_blue。
同样的样式可以被任意数量的对象使用。
样式可以被级联,这意味着可以将多个样式分配给一个对象,每个样式可以具有不同的属性。因此,并非所有的属性都必须在一个样式中指定。 LVGL将在样式中搜索属性,直到找到定义该属性的样式,或者如果没有任何样式指定,则使用默认值。 例如, style_btn 可以生成默认的灰色按钮,而 style_btn_red 可以仅添加 background-color=red 覆盖背景颜色。
最后的样式具有更高的优先级。这意味着,如果一个属性在两个样式中都指定了,对象中最新的样式将被使用。
一些属性(例如文字颜色)可以从父级继承,如果没有在对象中指定的话。
对象的本地样式比“正常”样式的优先级更高。
与CSS不同(在CSS中,伪类描述不同的状态,例如 :focus ),在LVGL中,属性分配给了给定的状态。
当对象更改状态时,可以应用过渡效果。
States(状态)
对象可以处于以下状态的组合中:
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_STATE_DEFAULT 的属性。 如果即使对于默认状态,某个属性也未设置,则将使用默认值。
但是,“与之最匹配的状态的属性”到底是什么意思呢?状态有一个优先级,由它们的值表示(请参见上面的列表)。值越高,优先级越高。 为了确定使用哪个状态的属性,我们来看一个例子。假设背景颜色定义如下:
LV_STATE_DEFAULT:白色
LV_STATE_PRESSED:灰色
LV_STATE_FOCUSED:红色
初始情况下,对象处于默认状态,所以情况很简单:属性在对象当前状态中完全定义为白色。
当对象被按下时,有两个相关属性:白色的默认属性(默认与每个状态相关)和灰色的按下属性。按下状态的优先级为0x0020,高于默认状态的优先级0x0000,因此会使用灰色。
当对象处于聚焦状态时,与按下状态发生的情况相同,将使用红色。(聚焦状态的优先级高于默认状态)
当对象处于聚焦和按下状态时,灰色和红色都可以。但是按下状态的优先级高于聚焦状态,因此会使用灰色。
可以为 LV_STATE_PRESSED | LV_STATE_FOCUSED 设置玫瑰色,此组合状态的优先级为0x0020 + 0x0002 = 0x0022,高于按下状态的优先级,因此将使用玫瑰色。
当对象处于选中状态时,没有属性来设置此状态的背景颜色。由于缺乏更好的选择,对象仍然保持默认状态的白色属性。
一些实用的注意事项:
状态的优先级(值)非常直观,这是用户自然期望的结果。例如,如果对象处于聚焦状态,用户仍然希望看到它是否被按下,因此按下状态具有更高的优先级。如果聚焦状态具有更高的优先级,它将覆盖按下的颜色。
如果要为所有状态(例如红色背景颜色)设置属性,只需为默认状态设置即可。如果对象在其当前状态下找不到属性,它将退回到默认状态的属性。
使用 OR 运算符来描述复杂情况下的属性(例如,按下 + 选中 + 聚焦)。
对于不同的状态,使用不同的样式元素可能是个好主意。例如,为释放、按下、选中 + 按下、聚焦、聚焦 + 按下、聚焦 + 按下 + 选中等状态找到背景颜色是相当困难的。相反,例如,使用按下和选中状态的背景颜色,并使用不同的边框颜色指示聚焦状态。
层叠样式
在一个样式中并不需要设置所有的属性。可以给一个对象添加更多的样式,并且后添加的样式可以修改或扩展外观。例如,创建一个通用的灰色按钮样式,然后再创建一个红色按钮的新样式,只设置新的背景颜色。
这就像在CSS中使用类列表一样,比如 <div class=".btn .btn-red">。
后添加的样式具有优先权,优先于先前设置的样式。因此,在上述灰色/红色按钮的例子中,应该先添加普通按钮样式,然后再添加红色样式。但是,状态的优先级仍然会被考虑。因此,让我们来看一个例子:
基本按钮样式为默认状态设置了暗灰色的颜色,为按下状态设置了浅灰色的颜色
红色按钮样式只在默认状态下将背景颜色设置为红色
在这种情况下,当按钮释放(处于默认状态)时,它将是红色的,因为最近添加的样式(红色样式)完全匹配。当按钮被按下时,浅灰色的颜色更匹配,因为它完美描述了当前的状态,所以按钮会是浅灰色的。
继承
一些属性(通常与文本相关的属性)可以从父对象的样式中继承。 只有在对象的样式中没有设置给定属性时(即使是默认状态下也是如此),继承才会应用。 在这种情况下,如果属性是可继承的,属性值将在父对象中搜索,直到某个对象为属性指定一个值。 父对象将使用自己的状态来确定值。因此,如果按钮被按下,并且文本颜色来自于这里,那么按下时文本颜色将会被使用。
Parts(部分)
物体可以由具有自己样式的“部分”组成。
在LVGL中,存在以下预定义部分:
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_style_t 类型的变量中。样式变量应该是静态的、全局的或者动态分配的。 换句话说,它们不能是函数内部的局部变量,在函数结束时会被销毁。 在使用样式之前,应该使用 lv_style_init(&my_style) 进行初始化。样式初始化后,可以添加或更改属性。
属性设置函数的格式如下: lv_style_set_<property_name>(&style, <value>)。例如:
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x115588));
lv_style_se
1015




被折叠的 条评论
为什么被折叠?



