LVGL作为EmWin的一个开源替代,在做国产替代项目经常用到,结合国产的MCU可以说是绝配。
这里汇总LVGL里面常用的一些设置和组件的使用。
一般用lv_imgbtn作为最常用的组件,结合坐标可以实现界面的完全自定义。
使用LVGL做大型的自定义界面,配置方面最好多带一块外置的Flash例如:
华邦(Winbond) 25Q128JVSIQ 128Mbit(16MB) 存储芯片
如果界面比16MB还要大,还可以用32MB的存储芯片
华邦(Winbond) W25Q256JVSIQ 256Mbit (32MB) 存储芯片
有了大的存储空间,自定义的UI就可以任意发挥了。
本篇把做自定义界面时最常用到的调用整理成速查手册,按“工具 → 全局设置 → 控件基础 → 图片按钮 → 图片资源 → 事件”分组,每个片段都注明用途与易错点。
工具软件
离线的图片转换工具,可以转换图片为C文件或者是bin文件
- LvglFontTool_V0.4
LVGL字库制作工具,
当要显示中文的时候,这个是必备工具,
把项目中要用到的文字制作成C文件嵌入到项目中来,就能显示中文。
常用的LVGL代码
调试日志输出
1
|
LV_LOG_USER("Clicked date: %02d.%02d.%d", date.day, date.month, date.year); //打印日历信息
|
日志输出尽量不要用printf()函数,后期作为正式产品输出的时候不方便禁用。LV_LOG_* 系列的好处是可分级(TRACE/INFO/WARN/ERROR)且集中管理:开发期开 TRACE,量产把级别调到 WARN 就安静了,散落在各处的 printf 做不到这一点。使用前需在 lv_conf.h 打开 LV_USE_LOG,并用 lv_log_register_print_cb 注册输出函数(或定义 LV_LOG_PRINTF 直接走 printf)。
禁止屏幕滑动和出现滚动条
1
|
lv_obj_clear_flag(lv_scr_act(),LV_OBJ_FLAG_SCROLLABLE);
|
一般主屏幕设置成禁止滚动会比较好。原因:自定义界面都是绝对坐标摆位的,控件一旦比屏幕大(或加了边距),屏幕对象就获得滚动能力,误触拖动时所有控件会整体跑偏。LV_OBJ_FLAG_SCROLLABLE 是对象级标志,哪个对象内容溢出哪个才会滚。
位置,文本,事件绑定
1
2
3
4
5
6
|
lv_obj_set_pos(btn1, 20, 20);//设置坐标
lv_obj_set_size(btn1, 150, 50);//设置大小
lv_obj_set_event_cb(btn1, btn_event_cb);//设置回调函数
lv_obj_t* label = lv_label_create(btn1, NULL);//给 btn1 添加 label 子对象
lv_label_set_text(label, "Click me");//设置文本
|
注意版本差异:lv_obj_set_event_cb 是 LVGL v7 的接口,v8 里改为 lv_obj_add_event_cb(btn1, btn_event_cb, LV_EVENT_ALL, NULL)——v8 一个对象可以挂多个回调、并按事件过滤。label 以 btn1 为父对象创建,按钮动它跟着动,这是父子坐标系的用法。
创建图片按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
|
static lv_obj_t* create_menu_button(lv_obj_t *parent, const void *press_icon,
const void *relese_icon, lv_coord_t x, lv_coord_t y) {
lv_obj_t *icon = lv_imgbtn_create(parent);
lv_imgbtn_set_src(icon, LV_IMGBTN_STATE_CHECKED_RELEASED, relese_icon, NULL,
NULL);
lv_imgbtn_set_src(icon, LV_IMGBTN_STATE_CHECKED_PRESSED, press_icon,
NULL,
NULL);
lv_imgbtn_set_state(icon, LV_IMGBTN_STATE_CHECKED_RELEASED);
lv_obj_set_pos(icon, x, y);
lv_img_set_antialias(parent, false);
return icon;
}
|
图片按钮的核心是“一套状态对应一套图”:为 CHECKED_RELEASED(选中且松开)、CHECKED_PRESSED(选中且按下)分别指定贴图,之后用 lv_imgbtn_set_state 切状态,贴图自动跟着换。后两个 NULL 是三段式贴图(左/中/右)布局,普通整图按钮留空即可。事件处理函数里按 lv_event_get_code 的返回值分流:
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
36
|
/**
* 手动点击接水事件
* @param event
*/
void menu_start_event_cb(lv_event_t *event) {
lv_event_code_t code = lv_event_get_code(event); //获取事件code
if (code == LV_EVENT_PRESSED) {
/* 对象被长按 */
//按下 水机开始工作
water_machine_start_work();
//显示警告消息
show_warn_msg(WARN_CODE_CUP_HOT);
} else if (code == LV_EVENT_LONG_PRESSED) {
/* 对象被长按 */
//长按 水机开始工作
water_machine_start_work();
//显示警告消息
show_warn_msg(WARN_CODE_CUP_HOT);
} else if (code == LV_EVENT_LONG_PRESSED_REPEAT) {
/* 对象被重复长按 */
//长按 水机开始工作
water_machine_start_work();
//显示警告消息
show_warn_msg(WARN_CODE_CUP_HOT);
} else if (code == LV_EVENT_RELEASED) {
/* 对象被释放 */
//释放,停止出水
water_machine_stop_work();
//隐藏警告消息
show_warn_msg(WARN_CODE_HIDDEN);
}
if (code == LV_EVENT_VALUE_CHANGED) { //值改变事件处理
//lv_obj_t* obj = lv_event_get_current_target(e); //获取事件产生的对象
}
}
|
调用
1
2
3
4
5
|
//定义对象
lv_obj_t *menu_start;
//创建按钮
menu_start = create_menu_button(lv_scr_act(), &menu_tea_p,
&menu_tea_r, 621, 50);
|
使得图片按钮可以点击
1
|
lv_obj_add_flag(menu_start, LV_OBJ_FLAG_CLICKABLE);
|
禁止某个组件的点击功能
与上面相反,把 CLICKABLE 标志清掉即可:
1
|
lv_obj_clear_flag(menu_start, LV_OBJ_FLAG_CLICKABLE);
|
清掉后控件不再响应按下事件,点击会“穿透”给下层对象,适合做纯装饰性图片。
隐藏图片按钮
1
|
lv_imgbtn_set_state(menu_start, LV_IMGBTN_STATE_CHECKED_DISABLED);
|
修改图片状态为普通未按下状态
1
|
lv_imgbtn_set_state(menu_start, LV_IMGBTN_STATE_CHECKED_RELEASED);
|
修改图片状态为按下状态
1
|
lv_imgbtn_set_state(menu_start, LV_IMGBTN_STATE_CHECKED_PRESSED);
|
显示/隐藏控件
1
|
lv_obj_clear_flag(img_warn_msg, LV_OBJ_FLAG_HIDDEN);
|
LV_OBJ_FLAG_HIDDEN 是所有控件通用的隐藏标志:置上即不渲染(对象还在,坐标和父子关系不变),清掉即显示。比起反复创建/删除对象,切标志没有内存分配开销,弹窗类 UI 推荐这种做法;隐藏时记得连子对象一起考虑(隐藏父对象会连子对象一起藏)。
修改图片内容
同一个 img 对象运行中换图,两种来源写法不同:C 数组方式(资源编进固件,速度快、占内部 Flash)用指针;bin 方式(资源在外部存储,走文件系统)用 “盘符:路径” 字符串。换图后尺寸变了记得 lv_obj_set_size 或让 LVGL 自动按新图重算。将图片转换成C文件方式
1
2
3
4
|
//定义图片
LV_IMG_DECLARE(warn_cup_block);
//更改图片src
lv_img_set_src(img_warn_msg, &warn_cup_block);
|
将图片转换成bin文件方式:
1
2
|
//需要配合FatFS和提前将bin文件放到SD卡上
lv_img_set_src(img_warn_msg, "S:images/warn/warn_cup_block.bin");
|
手动触发事件
事件不只来自输入设备,代码里也能主动发。自定义事件(如 USER_EVENT_1)自己定义枚举,从 _LV_EVENT_LAST 之后编号,就不会和系统事件撞号:
1
2
3
|
//下面2个函数手动触发事件
lv_event_send(btn1, USER_EVENT_1, &test_data);//内部调用lv_event_send_func
lv_event_send_func(btn_event_cb, btn1, LV_EVENT_REFRESH, &test_data1);
|
事件清单
这份枚举就是 lv_event_get_code 的全部返回值,按三组记:按下类(PRESSED/PRESSING/CLICKED/RELEASED/长按系列)、拖动与手势类、状态变化类(VALUE_CHANGED/FOCUSED/DELETE 等)。写回调时先想清楚要的是“边沿”还是“电平”:CLICKED 是完整的一次点按,PRESSED 是按下瞬间,LONG_PRESSED 要按住超过 LV_INDEV_LONG_PRESS_TIME 才来。以你手上版本的 lv_event.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
26
|
enum {
LV_EVENT_PRESSED, /**< The object has been pressed*/
LV_EVENT_PRESSING, /**< The object is being pressed (called continuously while pressing)*/
LV_EVENT_PRESS_LOST, /**< User is still pressing but slid cursor/finger off of the object */
LV_EVENT_SHORT_CLICKED, /**< User pressed object for a short period of time, then released it. Not called if dragged. */
LV_EVENT_LONG_PRESSED, /**< Object has been pressed for at least `LV_INDEV_LONG_PRESS_TIME`. Not called if dragged.*/
LV_EVENT_LONG_PRESSED_REPEAT, /**< Called after `LV_INDEV_LONG_PRESS_TIME` in every
`LV_INDEV_LONG_PRESS_REP_TIME` ms. Not called if dragged.*/
LV_EVENT_CLICKED, /**< Called on release if not dragged (regardless to long press)*/
LV_EVENT_RELEASED, /**< Called in every cases when the object has been released*/
LV_EVENT_DRAG_BEGIN,
LV_EVENT_DRAG_END,
LV_EVENT_DRAG_THROW_BEGIN,
LV_EVENT_GESTURE, /**< The object has been gesture*/
LV_EVENT_KEY,
LV_EVENT_FOCUSED,
LV_EVENT_DEFOCUSED,
LV_EVENT_LEAVE,
LV_EVENT_VALUE_CHANGED, /**< The object's value has changed (i.e. slider moved) */
LV_EVENT_INSERT,
LV_EVENT_REFRESH,
LV_EVENT_APPLY, /**< "Ok", "Apply" or similar specific button has clicked*/
LV_EVENT_CANCEL, /**< "Close", "Cancel" or similar specific button has clicked*/
LV_EVENT_DELETE, /**< Object is being deleted */
_LV_EVENT_LAST /** Number of events*/
};
|
踩坑记录与注意事项
- v7/v8 接口混用:set_event_cb(v7)与 add_event_cb(v8)、v8.2 前后 imgbtn 接口都有差异,从旧工程拷代码先对一遍当前版本的 lv_event.h / lv_imgbtn.h;
- 图片资源两种形式按需选:C 数组嵌入固件、读取快但占 Flash 且换图要重编;bin 放外部存储、换图只拷文件但要做文件系统。大 UI 推荐后者(见文首的外挂 Flash 方案);
- LV_LOG 要先使能:lv_conf.h 里不开 LV_USE_LOG,LV_LOG_USER 什么都不输出;
- 禁了滚动还想要局部滚动:SCROLLABLE 是对象级的,主屏禁掉、列表容器单独保留即可,别全局一刀切;
- 事件回调里别做重活:回调在 lv_timer_handler 的上下文里执行,长循环会拖死整个 GUI 刷新(包括喂狗),耗时操作发个标志位出去做;
- 贴图状态没配全:只设了 PRESSED 没设 RELEASED,切状态时会显示空白;imgbtn 的每个用到的状态都要有图。