LVGL基本概念

Creative Commons
本作品采用知识共享署名

本文介绍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
    2
    lv_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_MAINLV_BTN_PART_MAIN, Parts主要用来定义Widgets的Style。

Event

Event用在object交互或者变化时发生,例如点击,拖拽等。用户向object注册event callback,并在event callback中处理lvgl的event

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
28
29
30
31
32
33
34
lv_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退出编辑模式,进入导航模式。

参考

https://docs.lvgl.io/7.11/overview/index.html