初步学习 LVGL

从官方文档上学习 lvgl,包括小工具的使用和组件的绘制

Posted by Jerry Chen on August 10, 2020

工具篇

生成 lvgl 字体

使用 LvglFontTool 工具(最新 v0.4)生成 lvgl 上的字体 c 文件;

开始:

  1. 选择字体

    我选择“宋体” –> “常规” –> “12”

  2. 加入常用汉字,并清除重复

  3. 插入 awesome 字体图标,选择添加所有图标

    添加完后点确定即可

  4. 输入字体名称,点击开始转换

    我这里输入的是 lv_font_songti12

  5. 转换完成后点击保存,得到 lv_font_songti12.c

使用 LvglFont

  1. 将上一步得到的 lv_font_songti12.c 文件放在 lvgl/src/lv_font/ 目录下;

  2. 在应用源文件上方外部声明 lv_font_songti_12 字体资源;

    1
    
    LV_FONT_DECLARE(lv_font_songti_12);
    
  3. 新建一个样式 style1,使用 lv_font_songti_12 字体

    1
    2
    3
    
    static lv_style_t style1;
    lv_style_reset(&style1);
    lv_style_set_text_font(&style1, LV_STATE_DEFAULT, &lv_font_songti_12);
    
  4. 新建一个 label,使用 style1 样式

    源文件应使用 utf8 编码,\uF004 是 awesome 中的爱心图标♥

    显示效果:[左上角] “中文 english ♥”

    1
    2
    3
    4
    
    lv_obj_t *label = lv_label_create(lv_scr_act(), NULL);
    lv_obj_add_style(label, LV_OBJ_PART_MAIN, &style1);
    lv_label_set_text(label, "中文 english \uF004");
    lv_obj_align(label, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
    

生成 lvgl 单色图片

工具1:

lvgl 在线图片转换

工具2:

Image2Lcd v3.2(右下角注册:0000-0000-0000-0000-6A3B)

开始:

  1. 使用 Image2Lcd 打开一张图片进行处理;

  2. 处理为单色,输出为 img_test.bmp

    得到的图片可用 “Windows 画图” 继续编辑修改;

  3. 打开 lvgl 在线图片转换,选择上一步的 bmp 文件;

  4. 输入名称 img_test,Color format 选择 “Indexed 2 colors”;

  5. 点击转换后生成 img_test.c

使用 lvgl 图片

  1. img_test.c 加入到工程;

  2. 在应用源文件上方外部声明 img_test 图片资源;

    1
    
    LV_IMG_DECLARE(img_test);
    
  3. 新建一个图像 img1 使用 img_test 资源;

    1
    2
    3
    
    lv_obj_t *img1 = lv_img_create(lv_scr_act(), NULL);
    lv_img_set_src(img1, &img_test);
    lv_obj_align(img1, NULL, LV_ALIGN_CENTER, 0, 0);
    

基础篇

基础

信息

任务处理都在(动态的都在这里执行):

lv_task_handler();

系统时间相关(告诉 lvgl 时间过了多久),每隔 x 毫秒调用一次:

lv_tick_inc(x);

角度

lvgl 的角度范围是:0~360 度;

其中,正右方是 0 度,顺时针转,正下方是 90 度;

日志

启用功能:

1
2
#define LV_USE_LOG      1
#define LV_LOG_LEVEL    LV_LOG_LEVEL_INFO

具体实现:

能使用 printf 的开启下面的宏:

1
#define LV_LOG_PRINTF   1

不能使用 printf 的参考官方教程——日志

对象

下面只涉及基本属性;

大小和位置
1
2
3
4
5
6
7
8
# 大小固定
lv_obj_set_size(btn1, 100, 50);
# 大小适应内容
lv_btn_set_fit(btn1, true, true);
# 绝对位置
lv_obj_set_pos(btn1, 20,30);
# 使用对齐
lv_obj_align(btn1, NULL, LV_ALIGN_CENTER, 0, 0);
点击和拖动使能
1
2
lv_obj_set_click(btn1, true);
lv_obj_set_drag(btn, true);
父级

子对象位置 (0, 0) 是相对父对象的位置;

超出父对象大小的部分将不可见;

1
2
3
4
5
6
7
8
9
10
//par 父级是当前屏幕
lv_obj_t * par = lv_obj_create(lv_scr_act(), NULL);
lv_obj_set_size(par, 100, 80);

//child 父级是 par
lv_obj_t * child = lv_obj_create(par, NULL);
lv_obj_set_pos(child, 10, 10);

//也可以给已有对象设置父级
lv_obj_set_parent(obj, new_parent)
屏幕
1
2
3
4
5
6
7
8
9
10
11
# 获取当前活动屏幕
lv_obj_t * scr_act = lv_scr_act();

# 创建新的屏幕
lv_obj_t * scr2 = lv_obj_create(NULL, NULL);

# 加载屏幕
lv_scr_load(scr2);

# 删除屏幕,不能删除当前加载的屏幕
lv_obj_del(scr3);
创建和删除对象
1
2
3
4
5
6
7
8
9
10
11
# 创建对象
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_async(obj);

# 删除对象的所有子对象(不包括对象)
void lv_obj_clean(lv_obj_t * obj);
对象状态(状态可组合)

当用户按下、释放、焦点等对象时,库通常会自动更改状态。但是,也可以手动更改状态。

1
2
lv_obj_set_state(obj, part, LV_STATE...)
lv_obj_add/clear_state(obj, part, LV_STATE_...)
  • LV_STATE_DEFAULT 正常,释放
  • LV_STATE_CHECKED 切换或选中
  • LV_STATE_FOCUSED 通过键盘或编码器聚焦或通过触摸板/鼠标单击
  • LV_STATE_EDITED 编码器编辑
  • LV_STATE_PRESSED
  • LV_STATE_DISABLED 已禁用或非活动

图层

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使能单击子级设置到顶层(除 sys 悬浮层的最上层'悬浮层放鼠标图标等')
lv_obj_set_top(obj, true);

# 上移到前景
lv_obj_move_foreground(obj);
# 下移
lv_obj_move_background(obj);

# 获取顶层对象
lv_obj_t * obj_top = lv_layer_top();

# 设置父级,会置于父级的上层
lv_obj_set_parent(obj, new_parent);

核心篇

事件

分配回调函数:

多个对象可以使用同一个回调函数;

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
35
lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);
lv_obj_set_event_cb(btn, my_event_cb);

...

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.*/
}

所有的事件,看官方文档——事件类型

  • 与输入相关的事件
  • 与指针拖动相关的事件
  • 与特定对象相关的事件
  • 库发送的事件——LV_EVENT_DELETE 正在删除对象

手动发送事件:

1
2
3
4
5
6
7
8
9
# 发送事件带数据
lv_event_send(mbox, LV_EVENT_VALUE_CHANGED, "xxx");

# 获取当前对象的 LV_EVENT_VALUE_CHANGED 事件附带的数据
# 只在回调函数中使用
lv_event_get_data();

# 发送事件仅刷新
lv_event_send(label, LV_EVENT_REFRESH, NULL);

样式

太多了,直接看官方文档——样式

输入设备

枚举值有 LV_KEY_PREVLV_KEY_NEXTLV_KEY_ENTER 等;

直接看官方文档——输入设备

动画

直接看官方文档——动画

任务

创建任务

需要先设置回调函数 task_cb;

1
2
3
4
5
# 创建任务
lv_task_create(task_cb, period_ms, LV_TASK_PRIO_OFF/LOWEST/LOW/MID/HIGH/HIGHEST, user_data);

# 删除任务
lv_task_del(task_cb);

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void my_task(lv_task_t * task)
{
  /*Use the user_data*/
  uint32_t * user_data = task->user_data;
  printf("my_task called with user data: %d\n", *user_data);

  /*Do something with LVGL*/
  if(something_happened) {
    something_happened = false;
    lv_btn_create(lv_scr_act(), NULL);
  }
}

...

static uint32_t user_data = 10;
lv_task_t * task = lv_task_create(my_task, 500, LV_TASK_PRIO_MID, &user_data);
设置参数

修改已存在任务的参数;

  • lv_task_set_cb(task, new_cb)
  • lv_task_set_period(task, new_period)
  • lv_task_set_prio(task, new_priority)
任务控制

任务建立后,是由库定时调用的(lv_task_handler 函数);

一次性任务是,库首次调用任务后,将自动删除该任务;

不等待计时,立刻就绪:lv_task_ready(task)

一次性任务:lv_task_once(task)

重置任务:lv_task_reset(task)

异步调用

以下是示例,当前屏幕未使用后删掉它;

1
2
3
4
5
6
void my_screen_clean_up(void * scr)
{
  lv_obj_del(scr);  
}

lv_async_call(my_screen_clean_up, lv_scr_act());

组件篇

按钮

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
35
static lv_style_t style_btn;

//创建一个简单的按钮风格
lv_style_init(&style_btn);
//圆角:10
lv_style_set_radius(&style_btn, LV_STATE_DEFAULT, 10);
//透明度:不透明
lv_style_set_bg_opa(&style_btn, LV_STATE_DEFAULT, LV_OPA_COVER);
//背景:白色(起点)
lv_style_set_bg_color(&style_btn, LV_STATE_DEFAULT, LV_COLOR_WHITE);
//背景渐变:终点白色-以下不设置吧,下同
//lv_style_set_bg_grad_color(&style_btn, LV_STATE_DEFAULT, LV_COLOR_WHITE);
//渐变方向:从上到下
//lv_style_set_bg_grad_dir(&style_btn, LV_STATE_DEFAULT, LV_GRAD_DIR_VER);
//渐变起始点:0:在左/上最位置,255:在右/下最位置
//lv_style_set_bg_main_stop(&style, LV_STATE_DEFAULT, 0);
//渐变终止点:0:在左/上最位置,255:在右/下最位置
//lv_style_set_bg_grad_stop(&style, LV_STATE_DEFAULT, 0);
//设置边框
lv_style_set_border_color(&style_btn, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_style_set_border_opa(&style_btn, LV_STATE_DEFAULT, LV_OPA_COVER);
lv_style_set_border_width(&style_btn, LV_STATE_DEFAULT, 2);
//设置文字风格
lv_style_set_text_color(&style_btn, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_style_set_text_font(&style_btn, LV_STATE_DEFAULT, LV_THEME_DEFAULT_FONT_NORMAL);

//创建按钮使用上述风格
lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);     //添加按钮到当前屏幕
lv_obj_set_pos(btn, 10, 10);                            //设置位置
lv_obj_set_size(btn, 60, 30);                           //设置大小
lv_obj_reset_style_list(btn, LV_BTN_PART_MAIN);         //从主题中删除风格
lv_obj_add_style(btn, LV_BTN_PART_MAIN, &style_btn);    //添加自定义风格

lv_obj_t * label = lv_label_create(btn, NULL);          //按钮上加入子标签
lv_label_set_text(label, "Button");                     //设置标签文字

标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 长标签:超过宽度自动换行
lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
lv_label_set_long_mode(label1, LV_LABEL_LONG_BREAK);
lv_label_set_align(label1, LV_LABEL_ALIGN_CENTER);
lv_label_set_text(label1, "long long line of a label emmm... and emmmmmm...");
lv_obj_set_width(label1, 120);
lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, -30);

# 长标签:使用滚动
lv_obj_t * label2 = lv_label_create(lv_scr_act(), NULL);
lv_label_set_long_mode(label2, LV_LABEL_LONG_SROLL_CIRC);
lv_obj_set_width(label2, 120);
lv_label_set_text(label2, "It is a circularly scrolling text.");
lv_obj_align(label2, NULL, LV_ALIGN_CENTER, 0, 30);

圆弧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static lv_style_t style1;
lv_style_init(&style1);
lv_style_set_bg_color(&style1, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_width(&style1, LV_STATE_DEFAULT, 0); //Hide the background arc transparent

static lv_style_t style2;
lv_style_init(&style2);
lv_style_set_line_color(&style2, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_style_set_line_width(&style2, LV_STATE_DEFAULT, 3);

/*Create an Arc*/
lv_obj_t * arc = lv_arc_create(lv_scr_act(), NULL);
lv_obj_add_style(arc, LV_OBJ_PART_MAIN, &style1);
lv_obj_add_style(arc, LV_ARC_PART_INDIC, &style2);

lv_obj_set_size(arc, 120, 120);
lv_arc_set_bg_angles(arc, 0, 360);
lv_arc_set_angles(arc, 0, 270);
lv_obj_align(arc, NULL, LV_ALIGN_CENTER, 0, 0);

这里只是抛砖引玉,其他请参考 LVGL 官方文档;