本文介绍LVGL显示和输入相关基本概念,可作为使用和理解LVGL的基础。
一般情况下入门一个GUI,最先关注的是其直接可视化的组件,并且以会做一个demo表示入门。但接下来要实际使用GUI才发现理解该GUI的一些基本概念才能真正开发出可用的用户界面。因此最开始入门需要将注意力放在基本概念和机制的理解,而对组件只是大致了解,到实际使用的时候再进行关注学习。本文从显示,对象,事件,输入几个方面介绍LVGL的基本概念。
显示
Display
LVGL显示的示意图如下:
在LVGL中一个物理显示器对应一个Display,LVGL支持多个Display(物理显示器)。
每个Display有三个layer,上下关系如上图:
- layer_sys: 位于最上面,用于放置鼠标的光标
- layer_top: 位于中间,在layer_sys之下,用于放置pop-up和menu bar
- screen layer: 位于最下,用于放置各种要显示的gui对象。
显示时上面的layer的内容会遮挡下面的layer内容。
display与物理显示器对应,软件上就是与显示器驱动对应,lv_disp_drv_register注册一个显示器驱动,lvgl就增加一个display.
lvgl维护一个默认display,所有的object创建都是在默认display下,可以通过lv_disp_set_default指定默认display。
screen
screen上可以添加各种Lvgl支持的Widgets, 由Widgests组成用户界面。可以为一个display创建多个screen,但某一时刻上在screen layer上只会显示一个screen。使用lv_scr_load设置默认screen,也就是当前display上要显示的screen。
screen也是一个object,后文会做说明。
object
object是用户界面的基本构建块,包括Lvgl支持的所有Widgets. screen也是object.
属性
基本属性:所有Widgets都包含的属性
- 位置Position
- 大小Size
- 父亲Parent
- 可拖拽Drag enable
- 可以点击Click enable
一般通过lv_obj_set_…和lv_obj_get_…来进行操作这些基本属性
特殊属性:不同Wigests特有的属性,有各自的API进行操作
工作机制
object使用父子树形结构,只允许有一个父节点,允许有多个子节点。screen做为根节点,允许没有父节点。
移动父节点时,子节点跟随父节点一起移动,子节点相对于父节点的位置不变。子节点只在父节点内可见。
新创建的object处于前景,会遮挡之前创建的object。object通过下面几种方法变为前景:
- 当object被设置可以位于top后(lv_obj_set_top(obj, true)), object或者其子节点被点击,lvgl将自动将object移动到前景
- 使用
lv_obj_move_foreground(obj)
- 使用
lv_obj_set_parent(obj, new_parent)
如果new_parent在前景那么obj也变为前景
可以对object进行创建和删除,删除后object将不占用内存,为了节省内存,可以在不显示或者不使用object时将其删除。1
2lv_obj_t * lv_ <type>_create(lv_obj_t * parent, lv_obj_t * copy);
void lv_obj_del(lv_obj_t * obj);
lv_obj_del会立即删除object,但如果想在子节点的event处理中删除父节点,就需要用异步删除1
void lv_obj_del_async(lv_obj_t * obj)
如果想删除父节点下所有子节点使用1
void lv_obj_clean(lv_obj_t * obj);
状态
object处于下面几种状态的组合:
- LV_STATE_DEFAULT Normal, released
- LV_STATE_CHECKED Toggled or checked
- LV_STATE_FOCUSED 通过输入设备获取到焦点
- LV_STATE_EDITED 被旋转编码器编辑
- LV_STATE_HOVERED 鼠标悬停 (not supported now)
- LV_STATE_PRESSED Pressed
- LV_STATE_DISABLED Disabled or inactive
通常情况下lvgl会根据输入自动更改object状态。也可以手动改变状态: - 覆盖object状态:
lv_obj_set_state(obj, part, LV_STATE...)
- 添加或者删除状态:
lv_obj_add/clear_state(obj, part, LV_STATE_...)
覆盖状态时,可以给予组合,例如lv_obj_set_state(obj, part, LV_STATE_PRESSED | LV_PRESSED_CHECKED)
screen
screen是没有父节点的特殊对象,当前显示的内容为激活的screen上的内容,使用lv_scr_act()
可以获取当前激活的screen.
可以使用lv_obj_t * scr1 = lv_obj_create(NULL, NULL);
创建新的screen,并用lv_scr_load(scr1).
加载显示该screen.
Parts
其它的object就是Widgets,一种Wigets可能会有多个parts, 例如button只有main part,而slider由background,indicator,knob三部分组成。
Parts的名字组成为LV_ + <TYPE> _PART_ <NAME>
, 例如LV_BTN_PART_MAIN
和LV_BTN_PART_MAIN
, Parts主要用来定义Widgets的Style。
Event
Event用在object交互或者变化时发生,例如点击,拖拽等。用户向object注册event callback,并在event callback中处理lvgl的event1
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
28
29
30
31
32
33
34lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);
lv_obj_set_event_cb(btn, my_event_cb); /*Assign an event callback*/
static void my_event_cb(lv_obj_t * obj, lv_event_t event)
{
switch(event) {
case LV_EVENT_PRESSED:
printf("Pressed\n");
break;
case LV_EVENT_SHORT_CLICKED:
printf("Short clicked\n");
break;
case LV_EVENT_CLICKED:
printf("Clicked\n");
break;
case LV_EVENT_LONG_PRESSED:
printf("Long press\n");
break;
case LV_EVENT_LONG_PRESSED_REPEAT:
printf("Long press repeat\n");
break;
case LV_EVENT_RELEASED:
printf("Released\n");
break;
}
/*Etc.*/
}
Event类型
通用event
所有的object都可以接收这些event
与输入设备相关
- LV_EVENT_PRESSED object已被按下
- LV_EVENT_PRESSING object被连续按下
- LV_EVENT_PRESS_LOST 没有在object上按下
- LV_EVENT_SHORT_CLICKED 按下后在LV_INDEV_LONG_PRESS_TIME之前释放
- LV_EVENT_LONG_PRESSED 按下事件超过LV_INDEV_LONG_PRESS_TIME
- LV_EVENT_LONG_PRESSED_REPEAT 按下后超过LV_INDEV_LONG_PRESS_TIME后,每超过一次LV_INDEV_LONG_PRESS_REP_TIME就发一次
- LV_EVENT_CLICKED 单击后释放
- LV_EVENT_RELEASED 对象被释放
例如在对象上单击一下会收到下面event
LV_EVENT_PRESSED->LV_EVENT_SHORT_CLICKED->LV_EVENT_CLICKED->LV_EVENT_RELEASED
在对象上长按后释放会收到下面event
LV_EVENT_PRESSED->LV_EVENT_LONG_PRESSED->LV_EVENT_LONG_PRESSED_REPEAT->LV_EVENT_LONG_PRESSED_REPEAT->LV_EVENT_CLICKED->LV_EVENT_RELEASED
指针相关
- LV_EVENT_DRAG_BEGIN 开始拖动
- LV_EVENT_DRAG_END 结束拖动
- LV_EVENT_DRAG_THROW_BEGIN 拖动丢出
键盘和旋转编码器相关
组的概念在后面输入设备章节介绍
- LV_EVENT_KEY object收到按键
- LV_EVENT_FOCUSED object在组内获取焦点
- LV_EVENT_DEFOCUSED object在组内失去焦点
一般event
- LV_EVENT_DELETE object被删除.
特殊event
这类event用于特定的对象
- LV_EVENT_VALUE_CHANGED object的值发生变化 (e.g. for a Slider)
- LV_EVENT_INSERT Something is inserted to the object. (Typically to a Text area)
- LV_EVENT_APPLY “Ok”, “Apply” or similar specific button has clicked. (Typically from a Keyboard object)
- LV_EVENT_CANCEL “Close”, “Cancel” or similar specific button has clicked. (Typically from a Keyboard object)
- LV_EVENT_REFRESH Query to refresh the object. Never sent by the library but can be sent by the user.
用户数据
一些事件可以携带自定义数据,例如LV_EVENT_VALUE_CHANGED。接收到event后使用const void * lv_event_get_data(void);
来获取数据。
手动发生event
可以使用lv_event_send(obj, LV_EVENT_..., &custom_data)
手动发送任意event。LV_EVENT_REFRESH
刷新event,用于用户通知object刷新自己,例如
- 在内容变化时,通知lable刷新
- 语言变化时,通知lable刷新
- 满足某些调节时,刷新button为可用
- 添加删除样式。
lv_event_send_refresh(obj) == lv_event_send(obj, LV_EVENT_REFRESH, NULL)
lv_event_send_refresh_recursive(obj) 给obj和其子节点发送LV_EVENT_REFRESH
Style
样式用来设置object的外观,一种style可以用于多个object,一个object的不同parts可以使用不同style.
style的集合就是主题Themes
输入设备
控制对象
lvgl通过lv_indev_drv_register注册输入设备驱动,和注册时默认的display绑定,其输入只对注册时绑定的display下的对象有效。要将输入设备绑定到其它display上,需要在注册前通过lv_disp_set_default改变默认display.
允许输入设备和display进行任意绑定:一个输入设备可以绑定到多个display(一控多屏),多个输入设备可以绑定到一个display上(多控一屏)。
分类
lvgl的输入设备分为4大类:
- 指针类:鼠标或者触摸屏, LV_INDEV_TYPE_POINTER
- 键盘类:普通键盘或者数字键盘, LV_INDEV_TYPE_KEYPAD
- 按键类:外部硬件按键对应到屏幕上特定的按键,LV_INDEV_TYPE_BUTTON
- 旋转编码器:左右旋和按下行为,LV_INDEV_TYPE_ENCODER
在使用键盘和旋转编码器作为输入设备时,需要将被控制的object添加到”group”,在每一个group内只有一个焦点对象接收输入设备动作。
输入设备需要和group关联,一个输入设备只能关联一个group,但一个group可以关联多个输入设备。
导航和编辑模式
这两种模式常用于键盘和选择编码器
导航模式下LV_KEY_LEFT/RIGHT 被转为 LV_KEY_NEXT/PREV,用于在object之间切换,LV_KEY_ENTER改变状态进入编辑模式。
编辑模式下LV_KEY_NEXT/PREV用于编辑,短按或者长按LV_KEY_ENTER退出编辑模式,进入导航模式。