0%

LVGL学习记录

1. Objects

1.1 父部件和子部件

子部件的位置是相对父部件的,父部件移动子部件跟着移动

1
2
3
4
5
lv_obj_t * parent = lv_obj_create(lv_scr_act());   /*Create a parent object on the current screen*/
lv_obj_set_size(parent, 100, 80); /*Set the size of the parent*/

lv_obj_t * obj1 = lv_obj_create(parent); /*Create an object on the previously created parent object*/
lv_obj_set_pos(obj1, 10, 10); /*Set the position of the new object*/

部件可以动态创建和删除,删除父部件会递归删除子部件:

1
2
3
lv_obj_t * lv_<widget>_create(lv_obj_t * parent, <other parameters if any>);

void lv_obj_del(lv_obj_t * obj);

在子部件中删除父部件:

1
lv_obj_del_async(obj)  lv_timer_handler()  LV_EVENT_DELETE

其他删除函数:

1
2
3
lv_obj_clean(obj)              // 移除某一父部件的所有子部件

lv_obj_del_delayed(obj, 1000) // 延时删除

1.1.1 从父部件中获取子部件

对于子部件使用

1
2
3
4
5
lv_obj_set_parent(obj, new_parent)  // 为子部件设置新的父部件
lv_obj_get_parent(obj) // 获取父部件
lv_obj_get_index(obj) // 获取子部件在其父部件中的index
lv_obj_move_to_index(obj, index) // 改变子部件在父部件中顺序
lv_obj_swap(obj1, obj2) // 交换两个子部件

对于父部件使用

1
2
lv_obj_get_child(parent, idx)      // 在父部件中获取子部件
lv_obj_get_child_cnt(parent) // 获取子部件的个数
  • idx 表示子部件加入父部件时的顺序,第一个为 0,第二个为 1,最后一个为 -1

示例:https://docs.lvgl.io/8.3/widgets/extra/list.html#sorting-a-list-using-up-and-down-buttons

2.Screen

屏幕没有父对象

1
lv_scr_act()

2.1 Layers

层的概念:如果两个部件部分重叠,那么就产生了“层”的概念,上层部件会显示,下层部件会被遮盖,有以下几种方式来改变部件的层:

  1. lv_obj_move_foreground(obj)

  2. lv_obj_move_background(obj)

  3. lv_obj_move_to_index(obj, idx),从 0child_num-1 对应背景到前景,支持 idx 为负数

    1
    lv_obj_move_to_index(obj, lv_obj_get_index(obj) - 1)
  4. lv_obj_swap(obj1, obj2)

  5. lv_obj_set_parent(obj, new_parent),为 obj 设置新的父部件,obj 将会成为新的父部件的前景

两个自动创建的层:

  • top layer:总是在所有对象之上,可以用来显示提示窗口,全局菜单
  • system layer:在 top layer 之上,强制用来显示系统层面的东西,例如如鼠标指针

吸收点击焦点,只能点击 top_layer 上的控件

1
lv_obj_add_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);

3.Parts

4.States

5.snapshot

将一个 LVGL对象中转换为快照图片,或者将快照图片描绘在一个对象上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** Take snapshot for object with its children.
*
* @param obj The object to generate snapshot.
* @param cf color format for generated image.
*
* @return a pointer to an image descriptor, or NULL if failed.
*/
lv_img_dsc_t * lv_snapshot_take(lv_obj_t * obj, lv_img_cf_t cf);


/**
* Set the image data to display on the object
* @param obj pointer to an image object
* @param src_img 1) pointer to an ::lv_img_dsc_t descriptor (converted by LVGL's image converter) (e.g. &my_img) or
* 2) path to an image file (e.g. "S:/dir/img.bin")or
* 3) a SYMBOL (e.g. LV_SYMBOL_OK)
*/
void lv_img_set_src(lv_obj_t * obj, const void * src);

目前仅支持以下几种颜色格式:

  • LV_IMG_CF_TRUE_COLOR_ALPHA
  • LV_IMG_CF_ALPHA_1BIT
  • LV_IMG_CF_ALPHA_2BIT
  • LV_IMG_CF_ALPHA_4BIT
  • LV_IMG_CF_ALPHA_8BIT

注意,如果想要为对象更换一张新的图片,需要先手动释放该对象上的原图片:

1
2
3
4
5
6
7
8
9
10
11
void update_snapshot(lv_obj_t * obj, lv_obj_t * img_snapshot)
{
// 获取原图片并释放
lv_img_dsc_t* snapshot = (void*)lv_img_get_src(img_snapshot);
if(snapshot) {
lv_snapshot_free(snapshot);
}
// 添加新的图片
snapshot = lv_snapshot_take(obj, LV_IMG_CF_TRUE_COLOR_ALPHA);
lv_img_set_src(img_snapshot, snapshot);
}

2. Positions, sizes, layouts

image-20240418170400427

2.1 设置位置大小

设置位置的几种方法:

  1. lv_obj_set_x(btn, 10):指定坐标
  2. lv_obj_set_x(btn, lv_pct(50)):按照父部件的百分比设置
  3. lv_obj_set_pos(btn, 10 , 50)

2.2 改变对齐原点 Align

(1)嵌套内情况

1
2
lv_obj_set_align(obj, align);
lv_obj_align(obj, align, x, y);
  • 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

image-20240418170332715

特殊的,设置子部件位于父部件的中心:

1
2
3
4
lv_obj_center(obj);

//Has the same effect
lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);

如果父部件的大小改变,则使用相对位置的子部件的位置也会自动改变

(2)嵌套外情况

例如,想要一个标签对齐一个图片,这就是嵌套外情况,可以使用:

1
lv_obj_align_to(obj_to_align, reference_obj, align, x, y);

可选参考点:

  • 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 不会因大小改变而自动对齐

2.2 大小

设置大小的几种方法:

1
2
3
lv_obj_set_width(obj, 200); 
lv_obj_set_height(obj, 100);
lv_obj_set_size(obj, lv_pct(100), LV_SIZE_CONTENT);

可以指定大小,可以按照比例,也可以使用LV_SIZE_CONTENT,让部件按照其子部件大小来适当的调整自己的大小。

上面是设置边框大小,也可以设置内容大小,边框和内容之间会自动 padding:

1
2
lv_obj_set_content_width(obj, 50); //The actual width: padding left + 50 + padding right
lv_obj_set_content_height(obj, 30); //The actual width: padding top + 30 + padding bottom

2.3 使用 styles

使用风格有很多好处,上面都是用部件的本地风格去设置的,额外的创建风格,加到部件上:

  1. 统一设置,统一修改
1
2
3
4
5
6
static lv_style_t style;
lv_style_init(&style);
lv_style_set_width(&style, 100);

lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_add_style(btn, &style, LV_PART_MAIN);

留坑

2.4 平移-Translation

如果想要使得按键在被按下时向上移动一段距离,可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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_pct(50),这里是是指50% * height

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);

这种移动是真的移动,相关位置会变化的

2.5 变化-Transformation

如果想要使得按键在被按下时变大,可以使用:

1
2
3
4
5
6
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);

transform 会在原有尺寸上两侧各加上 10pt;注意,这种变化是一种视觉变化,并不是真的变大了

2.6 布局-Layout

1
lv_obj_set_layout(obj, <LAYOUT_NAME>)

将部件添加到一个布局中,会覆盖掉部件本身的大小、位置。

LVGL 支持量子布局:

  • Flexbox
  • Grid

(1) object 的 Flags

  • LV_OBJ_FLAG_HIDDEN 添加隐藏属性,该 object 会被布局忽略
  • LV_OBJ_FLAG_IGNORE_LAYOUT 该部件会被布局忽略
  • LV_OBJ_FLAG_FLOATING 部件会被布局忽略,并且不受 LV_SIZE_CONTENT 的改变

3. style

  • 多个样式可以赋给同一个部件,多个部件也可以使用相同的 style
  • LVGL 会搜索一个特性,直到某个 style 定义了它,如果没有就使用默认值
  • 如果未定义子部件的属性,子部件会从父部件继承该属性,这比默认值优先级更高
  • 可以设置 state 改变时,改变 style

3.1 States

所有可能的状态如下:

Styles — LVGL documentation

state 应用规则:

  • 数值越大,优先级越高,优先级越高,就显示对应 state 的 style
  • 例如 LV_STATE_FOCUSED | LV_STATE_PRESSED 则对应数值为二者之和
  • 如果优先级最高的 state 没有设置 style 则显示默认状态的 style

为部件添加以及获取 States 的函数如下:

1
2
3
4
5
void lv_obj_add_state(lv_obj_t * obj, lv_state_t state);

lv_state_t lv_obj_get_state(const lv_obj_t * obj);

bool lv_obj_has_state(const lv_obj_t * obj, lv_state_t state)

3.2 Parts

基本组成部分:Styles — LVGL documentation

3.3 使用 Style

(1) Style 本身

Style 必须是 static 或者动态内存分配的,不能是局部变量;并且使用之前需要调用lv_style_init初始化:

1
2
3
4
5
6
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x115588));
lv_style_set_bg_opa(&style_btn, LV_OPA_50);
lv_style_set_border_width(&style_btn, 2);
lv_style_set_border_color(&style_btn, lv_color_black());

移除 Style 中的某个属性:

1
lv_style_remove_prop(&style, LV_STYLE_BG_COLOR);

获取 Style 中的某个属性:

1
2
3
4
5
lv_style_value_t v;
lv_res_t res = lv_style_get_prop(&style, LV_STYLE_BG_COLOR, &v);
if(res == LV_RES_OK) { /*Found*/
do_something(v.color);
}

重置 Style(会释放其中所有属性的内存):

1
lv_style_reset(&style);

(2) Style 和 obj

给某个部件的 part 在某种 state 下添加 style:

1
lv_obj_add_style(btn, &btn_red, LV_PART_INDICATOR | LV_STATE_PRESSED | LV_STATE_CHECKED);
  • part 和 state 都是 selector 可以设置的参数

移除某个部件的所有 Style:

1
lv_obj_remove_style_all(obj)

具体移除某个部件的 Style:

1
lv_obj_remove_style(obj, style, selector)
  • 移除 style必须在函数的 selector 匹配设置 style时使用的 selector
  • style 设置为 NULL,则仅进行 selector的匹配
  • selector 可以使用 LV_STATE_ANYLV_PART_ANY 来匹配任意 state 和 part

获取 obj 的某个 property 的值:

1
2
3
lv_obj_get_style_<property_name>(obj, <part>)

PS:lv_color_t color = lv_obj_get_style_bg_color(btn, LV_PART_MAIN);

更新了 Style ,那已经设置好的 obj 怎么更新:Styles — LVGL documentation

(3) 转换效果-Transitions

当 obj 的 state 改变时,可以添加一个转场效果,主要设置如下:

  • 转场所需时间
  • 延迟多少 ms 之后开始转场
  • 转场动画路径
  • 哪些属性需要动画改变

示例如下:

1
2
3
4
5
6
7
8
9
/*Only its pointer is saved so must static, global or dynamically allocated */
static const lv_style_prop_t trans_props[] = {
LV_STYLE_BG_OPA, LV_STYLE_BG_COLOR,
0, /*End marker*/
};

static lv_style_transition_dsc_t trans1;
lv_style_transition_dsc_init(&trans1, trans_props, lv_anim_path_ease_out, duration_ms, delay_ms);
lv_style_set_transition(&style1, &trans1);

(4) 透明度(opa)、混合模式(blend mode)、角度(angle)、缩放(zoom)

这些属性设置时,LVGL会创建父部件及其子部件的 snapshot,这涉及是否有足够内存来使用,参考:Styles — LVGL documentation

设置选择角度或者缩放比例:

1
2
lv_obj_set_style_transform_angle(btn, 150, 0);        /*15 deg*/
lv_obj_set_style_transform_zoom(btn, 256 + 64, 0); /*1.25x*/
  • 角度:
  • 缩放:

3.4 Themes

主题是 Style 的集合,启用主题会自动应用于所有创建的部件,使用主题的方式:

1
2
3
4
5
6
7
8
9
10
lv_theme_t * th = lv_theme_default_init(
display, /*Use the DPI, size, etc from this display*/
LV_COLOR_PALETTE_BLUE,
LV_COLOR_PALETTE_CYAN, /*Primary and secondary palette*/
false, /*Light or dark mode*/
&lv_font_montserrat_10,
&lv_font_montserrat_14,
&lv_font_montserrat_18); /*Small, normal, large fonts*/

lv_disp_set_theme(display, th); /*Assign the theme to the display*/

在文件lv_conf.h 设置 LV_USE_THEME_DEFAULT 1 LVGL会自动使用默认主题。

嵌套扩展主题:链接

4. 滚动-Scroll

当子部件超过了父部件的大小,就会产生滚动,例如滚动查看文本框中的文字。

参考:链接

5. Events

为一个部件添加事件响应:

1
2
3
4
5
6
7
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_event_dsc_t *event_dsc = lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, NULL);

static void my_event_cb(lv_event_t * event)
{
printf("Clicked\n");
}

移除事件响应的两种方式:

1
2
lv_obj_remove_event_cb(obj, event_cb)     // 根据回调函数
lv_obj_remove_event_dsc(obj, event_dsc) // 根据 lv_obj_add_event_cb 的返回值

5.1 事件类型

  • Input device events:通用事件
  • Drawing events:通用事件
  • Other events:通用事件
  • Special events:特殊部件才有的
  • Custom events:自定义事件

详细事件种类:链接

(1) 自定义事件

需要在 LVGL 中注册一个 id,之后往这个 id 中手动发送事件就行:

1
uint32_t MY_EVENT_1 = lv_event_register_id();   // 注册一个自定义事件码

(2)手动发送事件

函数原型:

1
lv_res_t lv_event_send(lv_obj_t * obj, lv_event_code_t event_code, void * param)

示例代码:

1
2
3
/*Simulate the press of the first button (indexes start from zero)*/
uint32_t btn_id = 0;
lv_event_send(mbox, LV_EVENT_VALUE_CHANGED, &btn_id);

(3) 刷新事件

LV_EVENT_REFRESH 用来让用户通知一个部件,让它刷新自己,可能的使用场景:

  • label 的文本更新了,需要刷新
  • 系统语言改变了,label 需要刷新
  • 在某些情况下使能按键,需要让按键刷新

(4) 回调函数的参数

lv_event_t *e 中包括很多有用的信息:

  • lv_event_get_code(e) 获取事件码
  • lv_event_get_current_target(e) 获取触发事件的对象
  • lv_event_get_target(e) 获取原始触发事件的对象;如果不使能 event bubbling 功能,它和上面的函数获取的一样,反之,当子部件触发事件,对于父部件使用该函数会获取到子部件而非自己本身
  • lv_event_get_user_data(e) 获取注册回调函数 lv_obj_add_event_cb 时传递的参数
  • lv_event_get_param(e) 获取 lv_event_send 函数传递的参数

(5) Event bubbling

使用如下函数可以为 obj 添加 event bubbling 标志:

1
lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE)

含有该标志的 obj 会上传触发的事件到其父部件上,如果父部件同样有该标志,会继续传递给更上一层。

6. 输入设备

6.1 指针

6.1.1 光标

1
2
3
4
5
6
lv_indev_t * mouse_indev = lv_indev_drv_register(&indev_drv);

LV_IMG_DECLARE(mouse_cursor_icon); /*Declare the image source.*/
lv_obj_t * cursor_obj = lv_img_create(lv_scr_act()); /*Create an image object for the cursor */
lv_img_set_src(cursor_obj, &mouse_cursor_icon); /*Set the image source*/
lv_indev_set_cursor(mouse_indev, cursor_obj); /*Connect the image object to the driver*/

6.1.2 手势-Gestures

例如左滑和右滑,LVGL提供了基础的手势识别(部件滚动不算手势)。大部分部件默认会将手势事件 LV_EVENT_GESTRUE 上传给其父部件,最终上传到屏幕对象上,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void my_event(lv_event_t * e)
{
lv_obj_t * screen = lv_event_get_current_target(e);
lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());
switch(dir) {
case LV_DIR_LEFT:
...
break;
case LV_DIR_RIGHT:
...
break;
case LV_DIR_TOP:
...
break;
case LV_DIR_BOTTOM:
...
break;
}
}

lv_obj_add_event_cb(screen1, my_event, LV_EVENT_GESTURE, NULL);

想要阻止上传 LV_EVENT_GESTRUE 需要清楚标志 LV_OBJ_FLAG_GESTURE_BUBBLE

1
lv_obj_clear_flag(obj, LV_OBJ_FLAG_GESTURE_BUBBLE)

6.2 键盘和编码器

6.2.1 Group

需要将一个输入设备和一个组关联在一起,一个输入设备只能关联一个组,一个组中可以关联多个输入设备。

创建组,并为组添加对象:

1
2
lv_group_t * g = lv_group_create()
lv_group_add_obj(g, obj)

将输入设备和组进行关联:

1
lv_indev_set_group(indev, g)

例如,一个编码器输入设备的创建过程如下:

1
2
3
4
5
6
static lv_indev_drv_t indev_drv_3;
lv_indev_drv_init(&indev_drv_3); /*Basic initialization*/
indev_drv_3.type = LV_INDEV_TYPE_ENCODER;
indev_drv_3.read_cb = sdl_mousewheel_read;
lv_indev_t *enc_indev = lv_indev_drv_register(&indev_drv_3);
lv_indev_set_group(enc_indev, g);

重点是输入设备的回调函数,例如 PC 模拟器上的鼠标编码器:

1
2
3
4
5
6
7
8
9
10
void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
(void) indev_drv; /*Unused*/

data->state = wheel_state;
/* PC模拟器上是由系统获取,在单片机中可以提供一个获取函数在这里 */
/* data->enc_diff = get_encoder_data() */
data->enc_diff = wheel_diff;
wheel_diff = 0;
}

这个函数是和物理设备交互的关键函数,对于编码器而言,需要设置当前的编码值以及按键的状态两个成员参数;

该函数会在 LVGL 的定时任务中调用:

1
2
3
4
if(indev->driver->read_cb) {
INDEV_TRACE("calling indev_read_cb");
indev->driver->read_cb(indev->driver, data);
}

调用 read_cb 取得的数据会存储在 data 中,在定时任务的后半部中会根据输入设备的类型进行处理,例如对于编码器有:

1
2
3
4
5
6
7
8
9
10
11
12
if(data->enc_diff < 0) {
for(s = 0; s < -data->enc_diff; s++) {
lv_group_send_data(g, LV_KEY_LEFT); // 在编辑模式下发送,标识减少值或者向左移动
if(indev_reset_check(&i->proc)) return;
}
}
else if(data->enc_diff > 0) {
for(s = 0; s < data->enc_diff; s++) {
lv_group_send_data(g, LV_KEY_RIGHT);
if(indev_reset_check(&i->proc)) return;
}
}

其他输入设备的 data 可以查看 lv_indev_data_t 这个结构体的成员:

1
2
3
4
5
6
7
8
9
typedef struct {
lv_point_t point; /**< For LV_INDEV_TYPE_POINTER the currently pressed point*/
uint32_t key; /**< For LV_INDEV_TYPE_KEYPAD the currently pressed key*/
uint32_t btn_id; /**< For LV_INDEV_TYPE_BUTTON the currently pressed button*/
int16_t enc_diff; /**< For LV_INDEV_TYPE_ENCODER number of steps since the previous read*/

lv_indev_state_t state; /**< LV_INDEV_STATE_REL or LV_INDEV_STATE_PR*/
bool continue_reading; /**< If set to true, the read callback is invoked again*/
} lv_indev_data_t;

常见的一些输入设备中,例如触摸屏需要设置 point 成员,键盘需要设置 key 成员,编码器需要设置 enc_diff 成员。

6.2.2 编辑和导航模式

因为编码器的按键有限,所以 LVGL 定义了两种模式:编辑 (edit) 和 导航 (navigate),长按编码器来进行这两种模式的切换:

  • 编辑模式下转动编码器会给所属组发送 LV_KEY_LEFT/LV_KEY_RIGHT 键值
  • 导航模式下转动编码器会给所属组发送 LV_KEY_NEXT/LV_KEY_PREV 键值

短按编码器可以聚焦 (focus) 对象,对象状态为 LV_STATE_FOCUSED;如果对象进入编辑模式,对象状态为 LV_STATE_FOCUSED | LV_STATE_EDITED ,对于不同状态可以设置不同的 style

6.2.3 默认组

常用部件会自动添加到默认组中,不再需要手动调用lv_group_add_obj函数,我们可以创建一个组,然后将其设置为默认组:

1
2
3
lv_group_t * g = lv_group_create();   	
lv_group_set_default(g); // 设置默认组
lv_indev_set_group(my_indev, g); //将输入设备分配到默认组

哪些部件会被自动添加呢?部件的属性 group_defLV_OBJ_CLASS_GROUP_DEF_TRUE 会被自动添加,例如:

1
2
3
4
5
6
7
8
const lv_obj_class_t lv_btn_class  = {
.constructor_cb = lv_btn_constructor,
.width_def = LV_SIZE_CONTENT,
.height_def = LV_SIZE_CONTENT,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.instance_size = sizeof(lv_btn_t),
.base_class = &lv_obj_class
};

7. 颜色-Colors

lv_conf.h 中可以配置两个和颜色有关的宏:

1
2
3
4
5
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 32

/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0

LV_COLOR_16_SWAP 为 0 表示小字节序,通常来说都是小字节序;如果显示器需要大字节序则需要设置为 1

7.1 创建颜色的方式

7.1.1 手动创建

1
2
3
4
5
//All channels are 0-255
lv_color_t c = lv_color_make(red, green, blue);

//From hex code 0x000000..0xFFFFFF interpreted as RED + GREEN + BLUE
lv_color_t c = lv_color_hex(0x123456);

7.1.2 从调色板中衍生

1
2
3
4
5
lv_color_t c = lv_palette_main(LV_PALETTE_BLUE)

lv_color_t c = lv_palette_lighten(LV_PALETTE_BLUE, v) // v 是 1-5,值越大颜色越浅

lv_color_t c = lv_palette_darken(LV_PALETTE_BLUE, v) // v 是 1-4,值越大颜色越深

7.1.3 修改和混合颜色

1
2
3
4
5
6
7
8
9
10
11
// Lighten a color. 0: no change, 255: white
lv_color_t c = lv_color_lighten(c, lvl);

// Darken a color. 0: no change, 255: black
lv_color_t c = lv_color_darken(lv_color_t c, lv_opa_t lvl);

// Lighten or darken a color. 0: black, 128: no change 255: white
lv_color_t c = lv_color_change_lightness(lv_color_t c, lv_opa_t lvl);

// Mix two colors with a given ratio 0: full c2, 255: full c1, 128: half c1 and half c2
lv_color_t c = lv_color_mix(c1, c2, ratio);

7.2 不透明度-Opacity

LV_OPA_TRANSP 值为 0 ,代表透明;

LV_OPA_COVER值为255,代表完全不透明;

LV_OPA_50 代表半透明

8. 字体-fonts

需要在 lv_conf.h 中注意的设置:

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
// 1.颜色通道顺序
#define LV_USE_FONT_SUBPX 1
#if LV_USE_FONT_SUBPX
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
#endif

// 2. 编码方式
/**
* Select a character encoding for strings.
* Your IDE or editor should have the same character encoding
* - LV_TXT_ENC_UTF8
* - LV_TXT_ENC_ASCII
*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8

// 3.一行的显示方向
#define LV_USE_BIDI 1
#if LV_USE_BIDI
/*Set the default direction. Supported values:
*`LV_BASE_DIR_LTR` Left-to-Right
*`LV_BASE_DIR_RTL` Right-to-Left
*`LV_BASE_DIR_AUTO` detect texts base direction*/
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif

LVGL 支持 UTF-8 格式的 Unicode 字符,所以我们的文件也需要保存为 UTF-8 编码。

8.1 内建字体

LVGL 提供了 Montserrat 风格的不同字体大小的内建字体,具体参见:链接,使用 style 来设置:

1
lv_style_set_text_font(&my_style, &lv_font_montserrat_28);  /* 设置字号为 28 */

此外,LVGL 还提供了大量内建特殊字符图形:

image-20240319211022193

使用示例如下(编译时会自动拼接):

1
lv_label_set_text(my_label, LV_SYMBOL_OK "Apply" LV_SYMBOL_PLAY);

8.2 其他字体特征

8.3 添加中文字库

  1. 找到一款开源字体文件:链接

  2. 查找需要转换的字符的 Unicode 编码范围:链接

    1
    2
    // 常用汉字,ASCLL,标点符号
    0x4E00-0x9FA5,0x9FA6-0x9FFF,0x30-0x39,0x61-0x7a,0x41-0x5a,0x20-0x2F,0x3A-0x40,0x5B-0x60,0x7B-0x7E
  3. 在官方的在线字体转换网站上转换得到字符的 bitmap:[链接](Online font converter - TTF or WOFF fonts to C array | LVGL)

  4. 将生成的 .c 文件添加到路径 lvgl/src/font/ 目录下

方法1-局部使用字库(推荐)

1
2
3
LV_FONT_DECLARE(lv_font_siyuan_16);
lv_style_set_text_font(&style, &lv_font_siyuan_16 );
lv_obj_add_style(label1, &style, 0);

方法2-全局使用字库(不推荐)

在 lv_conf.h 中声明:

1
define LV_FONT_CUSTOM_DECLARE   LV_FONT_DECLARE(lv_font_siyuan_16)

8.4 添加新的符号

为某个符号制作 .c 文件,并添加到工程中的过程和 8.3 类似,但使用时有所不同:

  1. 将符号的 Unicode 值转换为 UTF-8 编码,例如0xf287 -> EF 8A 87

  2. 宏定义:\#define MY_USB_SYMBOL "\xEF\x8A\x87"

  3. 为风格添加字体,然后才能为 label 使用符号(符号相当于仅有一个字符的字库):

    1
    2
    3
    lv_style_set_text_font(&style, &lv_symbol_usb );
    lv_obj_add_style(label1, &style, 0);
    lv_label_set_text(label, MY_USB_SYMBOL);

符号获取网站:链接

Unicode 转 UTF-8 网站:链接

8.5 添加新的字符引擎

例如 FreeType,在运行时动态渲染,TODO。

10 Image

10.1 显示内建格式的图片

在线图片转换器:https://lvgl.io/tools/imageconverter

显示 .c 文件的原始图片:

1
2
3
4
5
LV_IMG_DECLARE(my_icon_dsc)

lv_obj_t * icon = lv_img_create(lv_scr_act(), NULL);
/*From variable*/
lv_img_set_src(icon, &my_icon_dsc);

10.2 使用 File System 和显示原始格式的图片

TODO

11. Animations

11.1 基本使用

动画的使用还是比较简单的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void anim_size_cb(void * var, int32_t v)
{
lv_obj_set_size(var, v, v);
}

lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, label); // 配置哪个部件需要动画
lv_anim_set_values(&a, 0, 100); // 设置值的变化区间
lv_anim_set_time(&a, 500); // 设置动画的执行时间
lv_anim_set_exec_cb(&a, lv_obj_set_x); // 设置需要变化的属性的设置函数
lv_anim_set_exec_cb(&a, anum_size_cb);
lv_anim_set_path_cb(&a, lv_anim_path_overshoot); // 设置动画效果路径
lv_anim_start(&a); // 开始动画

不同动画效果参见:链接,删除一个动画效果,传入部件以及对应属性的设置函数来完成:

1
bool lv_anim_del(void * var, lv_anim_exec_xcb_t exec_cb)

11.2 Timeline

TODO

12. Timer

LVGL 中 Timer 的实现是非抢占式的,手动创建的 Timer 会被 lv_timer_handler() 调用,因此 lv_timer_handler() 需要每隔几毫秒调用一次。

创建一个 Timer:

1
lv_timer_t * lv_timer_create(lv_timer_cb_t timer_xcb, uint32_t period, void * user_data)

period 的单位是 毫秒,user_data 不能是局部变量。例如:

1
2
3
4
5
6
7
8
void my_timer(lv_timer_t * timer)
{
uint32_t * user_data = timer->user_data;
printf("my_timer called with user data: %d\n", *user_data);
}

static uint32_t user_data = 10;
lv_timer_t * timer = lv_timer_create(my_timer, 500, &user_data);

创建完一个定时器之后,每隔 period 毫秒之后都会运行一次。

其他相关函数:

1
2
3
4
5
6
7
8
9
lv_timer_ready(timer)          // 在下一次调用 lv_timer_handler()时即刻运行回调函数

lv_timer_reset(timer) // 清空当前定时器的的计数值

lv_timer_set_cb(timer, new_cb) // 重新设置回调

lv_timer_set_period(timer, new_period) // 重新设置定时间隔

lv_timer_set_repeat_count(timer, count) // 设置定时器重复运行次数

12.1 闲置时间

通过调用如下函数可以获取 lv_timer_handler() 在上一次的调用中的空闲时间所占百分比:

1
uint8_t lv_timer_get_idle(void)

基于该值可以合理设置 lv_timer_handler() 的调用间隔。

简单

基础

图形化编程

输入组

移植