前言
本文是基于正点原子教程的LVGL部件使用笔记,原视频链接如下
【正点原子】手把手教你学LVGL图形界面编程【真人出镜】LVGL图形界面编程视频教程 单片机 嵌入式_哔哩哔哩_bilibili
因为本文是针对部件层,想对于每个部件留下一点记录,不涉及具体芯片的移植,关于移植,其实很看具体情况,首先配置显示,触摸可选,然后提供时基。然后看项目是否要上RTOS,是否有外部RAM,是否有自研的管理算法...
本文使用Windows模拟器开发,使用版本为LVGL8.3,具体模拟器配置可看这篇文章
LVGL在VScode中安装模拟器运行配置笔记教程_vscode lvgl-CSDN博客
同时这里也可以使用Trae和Qcode,直接问AI,问题解决更快。
更新日志
首次创建并发布博客------260417
在开始之前需要新建一个mygui的c文件,h文件,并在main.c的while循环前调用函数MyGui;
cpp
#include "lvgl/lvgl.h"
#include "mygui.h"
void MyGui(void)
{}
部件的基本属性
大小
C语言中没有"类"的概念,LVGL以结构体的形式来实现"类"的思维。这里先创建一个对象,显示效果为左图,当我们使用函数去定义这个对象的大小时,在下方加入一行代码,比如
lv_obj_set_size(obj, 320, 240); 显示的对象画布大小就会改变
cpp
void MyGui(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
}


API函数:
cpp
设置宽度:lv_obj_set_width(obj, new_width);
设置高度:lv_obj_set_height(obj, new_height);
同时设置宽度、高度:lv_obj_set_size(obj, new_width, new_height);
位置
这个概念也好理解,需要注意的是设置部件位置时,坐标原点在父对象的左上角
cpp
void MyGui(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj, 50, 50);
lv_obj_set_pos(obj, 100, 100);
}

API函数:
cpp
设置X轴坐标:lv_obj_set_x(obj, new_x);
设置Y轴坐标:lv_obj_set_y(obj, new_y);
同时设置X、Y轴坐标:lv_obj_set_pos(obj, new_x, new_y);
对齐
对齐方式有两种,一种是参照父对象对齐-左图,另一种是参照其他对象对齐(无父子关系)-右图
cpp
void MyGui(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj, 150, 150);
lv_obj_set_pos(obj, 50, 50);
lv_obj_t * son = lv_obj_create(obj);
lv_obj_set_size(son, 50, 50);
lv_obj_set_align(son, LV_ALIGN_CENTER);
}
cpp
void MyGui(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj, 150, 150);
lv_obj_set_pos(obj, 50, 50);
lv_obj_t * obj2 = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj2, 50, 50);
lv_obj_align_to(obj2, obj, LV_ALIGN_CENTER, 100, 0);
}
对齐位置有很多,这里用正点原子的图片用作参考

API:
cpp
参照父对象对齐:lv_obj_set_align(obj, LV_ALIGN_...);
参照父对象对齐,再进行偏移:lv_obj_align(obj, LV_ALIGN_..., x, y);
参照其他对象对齐(无父子关系),再进行偏移:lv_obj_align_to(obj_to_align, obj_referece, LV_ALIGN_..., x, y);
样式
样式用于设置部件的外观,以优化显示界面和实现用户交互。
有两种设置方式,添加普通样式和添加本地样式,我个人理解为是创建一种可以给所有对象的和仅限给单一一个对象的区别,具体可参考代码,这两个程序现象都是一样的。
cpp
void MyGui(void)
{
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0xf4b183));
lv_obj_t * obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj, 150, 150);
lv_obj_set_pos(obj, 50, 50);
lv_obj_add_style(obj, &style, LV_STATE_DEFAULT);
lv_obj_t * obj2 = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj2, 50, 50);
lv_obj_align_to(obj2, obj, LV_ALIGN_CENTER, 100, 0);
lv_obj_add_style(obj2, &style, LV_STATE_DEFAULT);
}
cpp
void MyGui(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj, 150, 150);
lv_obj_set_pos(obj, 50, 50);
lv_obj_set_style_bg_color(obj, lv_color_hex(0xf4b183), LV_STATE_DEFAULT);
lv_obj_t * obj2 = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj2, 50, 50);
lv_obj_align_to(obj2, obj, LV_ALIGN_CENTER, 100, 0);
lv_obj_set_style_bg_color(obj2, lv_color_hex(0xf4b183), LV_STATE_DEFAULT);
}


什么时候样式会生效:
bash
enum {
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, /* 禁用状态 */
...
};
有哪些样式属性可以设置?
很多很多,包括但不限于,背景轮廓边框阴影等,这里举个例子,现象可见上方右图
cpp
void MyGui(void)
{
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0x2196F3)); // 背景
lv_style_set_bg_opa(&style, LV_OPA_80);
lv_style_set_border_width(&style, 3);// 边框
lv_style_set_border_color(&style, lv_color_hex(0xFFFFFF));
lv_style_set_radius(&style, 15); // 圆角
lv_style_set_shadow_width(&style, 15);// 阴影
lv_style_set_shadow_ofs_x(&style, 5);
lv_style_set_shadow_ofs_y(&style, 5);
lv_obj_t * obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj, 150, 150);
lv_obj_set_pos(obj, 50, 50);
lv_obj_add_style(obj, &style, LV_STATE_DEFAULT);
}
如何单独设置部件中某个部分的样式?
在LVGL中,单独设置部件(Widget)某个部分(Part)的样式,使用 lv_obj_set_style_xxx 函数的**第3个参数(selector)**来指定部件部分。
cpp
enum {
LV_PART_MAIN = 0x000000, /* 主体,像矩形一样的背景 */
LV_PART_SCROLLBAR = 0x010000, /* 滚动条 */
LV_PART_INDICATOR = 0x020000, /* 指示器,指示当前值 */
LV_PART_KNOB = 0x030000, /* 手柄或旋钮,用于调整参数值 */
LV_PART_SELECTED = 0x040000, /* 选项框,指示当前选择的选项 */
LV_PART_ITEMS = 0x050000, /* 相似的元素,例如单元格 */
LV_PART_TICKS = 0x060000, /* 刻度 */
LV_PART_CURSOR = 0x070000, /* 光标 */
};
例如:
cpp
void MyGui(void)
{
lv_obj_t * slider = lv_slider_create(lv_scr_act());
lv_obj_set_size(slider, 200, 20);
lv_obj_center(slider);
lv_slider_set_value(slider, 50, LV_ANIM_OFF);
lv_obj_set_style_bg_color(slider, lv_color_hex(0xE0E0E0), LV_PART_MAIN);
lv_obj_set_style_radius(slider, 10, LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_hex(0x2196F3), LV_PART_INDICATOR);
lv_obj_set_style_radius(slider, 10, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider, lv_color_hex(0xFF5722), LV_PART_KNOB);
lv_obj_set_style_radius(slider, 15, LV_PART_KNOB);
lv_obj_set_style_size(slider, 30, LV_PART_KNOB);
}

API函数:上方示例中出现了一些,此处略
事件
LVGL中,当发生用户感兴趣的事情时,可以触发回调事件,以执行相关的操作,类似于单片机的中断回调函数。这里的回调函数可以检测事件类型以及触发对象。
cpp
lv_obj_t * btn;
static void my_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e); // 获取事件类型
lv_obj_t * obj = lv_event_get_target(e); // 获取触发对象
if(code == LV_EVENT_CLICKED && obj == btn) LV_LOG_USER("Clicked!");
}
void MyGui(void)
{
btn = lv_btn_create(lv_scr_act());
lv_obj_center(btn);
lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, NULL);
}

关于触发,可以有很多种,这里展示一部分(AI搜索)
| 事件 | 触发时机 |
|---|---|
LV_EVENT_PRESSED |
按下(首次接触) |
LV_EVENT_PRESSING |
正在按(持续触发) |
LV_EVENT_PRESS_LOST |
按下后移出对象释放 |
LV_EVENT_SHORT_CLICKED |
短按(按下+释放,无长按) |
LV_EVENT_LONG_PRESSED |
长按达到阈值 |
LV_EVENT_LONG_PRESSED_REPEAT |
长按持续重复触发 |
LV_EVENT_CLICKED |
点击(按下+释放,无移出) |
LV_EVENT_RELEASED |
释放 |
LV_EVENT_SCROLL_BEGIN |
滚动开始 |
LV_EVENT_SCROLL_END |
滚动结束 |
LV_EVENT_SCROLL |
滚动中 |
LV_EVENT_GESTURE |
手势识别(边缘滑动) |
LV_EVENT_KEY |
按键输入 |
LV_EVENT_FOCUSED |
获得焦点 |
LV_EVENT_DEFOCUSED |
失去焦点 |
LV_EVENT_LEAVE |
离开对象(鼠标/触摸移出) |
API函数
cpp
添加事件:lv_obj_add_event_cb(obj, event_cb, event_code, user_data);
删除事件:lv_obj_remove_event_cb(obj, event_cb);
部件使用
标签部件(lv_label)
在 LVGL 中,标签部件主要用于文本显示,例如标题、提示信息等。
组成部分:主体(LV_PART_MAIN),滚动条(LV_PART_SCROLLBAR),选中的文本(LV_PART_SELECTED)
设置文本有三种形式
① 直接设置文本,存储文本的内存动态分配 :lv_label_set_text( label, "hallo \n lvgl");
② 文本不存储在动态内存,而是在指定的缓冲区中(慎用):lv_label_set_text_static( label,"hallo" );
③ 格式化显示文本,类似printf :lv_label_set_text_fmt( label, "Value: %d", 50 ) ;
同样的可以对文本单独设置:
① 背景颜色:lv_obj_set_style_bg_color( label, lv_color_hex(0xffe1d4), LV_STATE_DEFAULT );
② 字体大小:lv_obj_set_style_text_font( label, &lv_font_montserrat_30, LV_STATE_DEFAULT );
③ 文本颜色:lv_obj_set_style_text_color(label, lv_color_hex(0xf7b37b), LV_STATE_DEFAULT );
示例:这里可以设置个别文本的字体颜色
cpp
void MyGui(void)
{
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello World!");
lv_obj_center(label);
}
cpp
void MyGui(void)
{
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_recolor( label, true );
lv_label_set_text(label, "Hello #ff0000 World!#");
lv_obj_center(label);
}


Q当文本长度超过部件大小的时候怎么显示?
默认情况下,如果没有限定标签部件大小,那它的大小自动扩展为文本大小。
也可以选择长文本模式
cpp
enum {
LV_LABEL_LONG_WRAP, /* 默认模式, 如果部件大小已固定,超出的文本将被剪切 */
LV_LABEL_LONG_DOT, /* 将 label 右下角的最后 3 个字符替换为点... */
LV_LABEL_LONG_SCROLL, /* 来回滚动 */
LV_LABEL_LONG_SCROLL_CIRCULAR, /* 循环滚动 */
LV_LABEL_LONG_CLIP, /* 直接剪切掉部件外面的文本部分 */
};
cpp
void MyGui(void)
{
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "This is a long piece of text that requires scrolling to be read completely. LVGL provides various long text handling modes.");
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); // 循环滚动
// 必须设置固定宽度,否则不会滚动
lv_obj_set_width(label, 200);
lv_obj_set_height(label, 30);
lv_obj_center(label);
lv_obj_set_style_text_color(label, lv_color_hex(0xffe1d400), 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_24, 0);
lv_obj_set_style_bg_color(label, lv_color_hex(0x333333), 0);
lv_obj_set_style_bg_opa(label, LV_OPA_COVER, 0);
}
示例效果:正在滚动

按钮部件(lv_btn)
在LVGL中,按钮部件与基础对象相比,没有新增任何功能。
按钮部件组成部分:主体(LV_PART_MAIN)
例程可参考上方事件,一个基础对象可以按下,一个按钮也能按下,倒也区别不大。
cpp
static void my_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e); // 获取事件类型
if(code == LV_EVENT_CLICKED) LV_LOG_USER("Clicked!");
else if(code == LV_EVENT_VALUE_CHANGED) LV_LOG_USER("Value changed");
}
void MyGui(void)
{
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_center(btn);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Click Me");
lv_obj_center(label);
lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, NULL);
}

部件拓展
中文字库
首先要准备需要使用的字体文件(ttf、otf等格式)
登录在线字体转换网站:https://lvgl.io/tools/fontconverter
添加字库文件到LVGL工程中,声明字体,编写测试代码
这里使用鸿蒙字体进行测试:

下载之后改名为.h文件,并添加到CMAKE目录中
另外8.3版本似乎不支持生成文件中的.static_bitmap = 0,,注释掉就好
cpp
#include "lvgl/lvgl.h"
#include "mygui.h"
#include "src/temp_16.h"
LV_FONT_DECLARE(temp_16)
void MyGui(void)
{
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "戏舟的嵌入式开源笔记");
lv_obj_center(label);
lv_obj_set_style_text_font(label, &temp_16, 0);
}


