一.LVGL的学习资料
欢迎阅读LVGL(v9.0)中文开发手册! — LVGL 文档
二.文件介绍
然后,我们选中这行右击选第二个就能到达该函数文件;
然后我们在这里编写代码;
三.屏幕介绍
屏幕的创建
注意:屏幕是没有父类的基础对象。
屏幕的结构
LVGL具有三层屏幕:
四.函数介绍
1.对象(lv_obj)
在LVGL中,所有对象都在 lv_obj_t 这个结构体的基础上演变而来。
(1)创建基础对象
代码:
·创建基础对象
lv_obj_t * obj = lv_obj_create(lv_scr_act());
(2)改变基础对象的大小
代码:
·设置大小
//设置宽度:
lv_obj_set_width(obj, new_width);
//设置高度:
lv_obj_set_height(obj, new_height);
//同时设置宽度和高度:
lv_obj_set_size(obj, new_ width, new_ height);
·获取大小
//获取宽度:
lv_obj_get_width(obj);
//获取高度:
lv_obj_get_height(obj);
(3)改变基础对象的位置和对齐方式
LVGL的远点坐标在右上角。
代码:
·设置位置
//设置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);
·对齐方式
根据不同的位置我们有不同的对齐方式,这里我们拿居中来演示。
//参照父对象对齐:
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);
·获取位置
//获取x轴坐标位置:
lv_obj_get_x(obj);
//获取y轴坐标位置:
lv_obj_get_y(obj);
(4)基础对象的样式
①基础对象的盒子模型概念
LVGL遵循CSS的border-box模型。
·边界(bounding):元素width和Height围起来的区域
·边框(border):相当于边框的厚度
·填充(padding):相当于内容以外的盒子填充区域。
·内容(content):显示其大小内容的区域。
·轮廓(outline):表示盒子与盒子之间的距离,和描边的线一个作用,这个线不占据空间。
②设置属性样式的具体步骤
·具体步骤:
1.创建基础对象
2.设置静态变量或全局变量
(注意:如果设置的是局部变量的化函数执行完了以后就结束了,所以会导致样式没有发生改变)
3.将样式初始化
4.给基础对象设置所需的样式
5.指定所要添加样式的基础对象
这里只是介绍了颜色的样式,这里面还有其他各种不同的样式。
样式函数的格式:lv_style_set_<property_name>(&style, <value>);
我们可以右键选择第一个进入其样式库,查找我们所需的样式使用。
所有的可用的样式属性我们可以在文档或者代码中获取得到。
文档位置: 英文原版:https://docs.lvgl.io/8.1/overview/style-props.html
中文翻译:http://lvgl.100ask.net/8.1/overview/style-props.html
③设置样式
·添加样式属性
1)普通样式
添加样式属性只需要前面声明了将其指向对象即可。
代码:
lv_obj_add_style(obj, &style_obj, 0); // 默认状态: LV_STATE_DEFAULT
·添加应用样式到对象
这里添加了被按下的应用样式。
代码:
lv_obj_add_style(obj, &style_obj, LV_STATE_PRESSED); // 按下状态,当对象被按下的时候应用该样式
此时原来的对象是白色,当用鼠标按下对象就会变成黑色,松开又变回白色的状态。
拓展:对象的一个部分也可以有自己的样式
对象可以处于以下状态的组合:
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) 禁用状态
LV_STATE_USER_1 (0x1000) 自定义状态
LV_STATE_USER_2 (0x2000) 自定义状态
LV_STATE_USER_3 (0x4000) 自定义状态
LV_STATE_USER_4 (0x8000) 自定义状态
对象可以有 部分(parts) ,它们也可以有自己的样式。LVGL 中存在以下预定义部分:
LV_PART_MAIN 类似矩形的背景
LV_PART_SCROLLBAR 滚动条
LV_PART_INDICATOR 指标,例如用于滑块、条、开关或复选框的勾选框
LV_PART_KNOB 像手柄一样可以抓取调整值
LV_PART_SELECTED 表示当前选择的选项或部分
LV_PART_ITEMS 如果小部件具有多个相似元素(例如表格单元格)
LV_PART_TICKS 刻度上的刻度,例如对于图表或仪表
LV_PART_CURSOR 标记一个特定的地方,例如文本区域或图表的光标
LV_PART_CUSTOM_FIRST 可以从这里添加自定义部件
2)本地样式
本地样式和普通样式是类似的,但是它不能在其他对象之间共享,可以说是一个对象的私有样式。
当使用本地样式,将自动分配局部样式,并删除对象释放时,本地样式对于向对象添加本地自定义很有用。
代码:
本地样式的接口函数格式:
lv_obj_set_style_<property_name>(obj, <value>, <selector>);
(5)基础对象的事件
·events
添加事件:
lv_obj_add_event_cb(obj, event_cb, event_code, user_data);
注意:
·事件类型(event_code)
输入设备事件(Input device events)
绘图事件(Drawing events)
其他事件(Special events)
特殊事件(Other events)
自定义事件(Custom events)
更全面的信息请查阅: 源码:lvgl/src/core/lv_event.h (lv_event_code_t)
开发文档: 英文:https://docs.lvgl.io/8.1/overview/event.html#event-codes
中文:http://lvgl.100ask.net/8.1/overview/event.html#event-codes
·事件冒泡
/* 事件冒泡 */
#if 1
static void my_event_cb(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e); // 获取触发事件的对象
lv_obj_t * parent = lv_event_get_current_target(e); // 获取触发事件对象的父对象(事件冒泡才有)
lv_event_code_t code = lv_event_get_code(e); // 获取当前部件触发的事件代码
lv_obj_t * label = lv_event_get_user_data(e); // 获取添加事件时传递的用户数据
switch(code){
case LV_EVENT_PRESSED:
lv_label_set_text(label, "LV_EVENT_PRESSED");
lv_obj_set_style_bg_color(parent, lv_color_hex(0xc43e1c), 0); // 通过本地样式(私有样式)设置背景色
lv_obj_set_style_bg_color(obj, lv_color_hex(0xc43e1c), 0); // 通过本地样式(私有样式)设置背景色
printf("LV_EVENT_PRESSED\n");
break;
case LV_EVENT_CLICKED:
lv_label_set_text(label, "LV_EVENT_CLICKED");
lv_obj_remove_local_style_prop(parent, LV_STYLE_BG_COLOR, 0); // 删除通过本地样式(私有样式)设置的背景色
lv_obj_remove_local_style_prop(obj, LV_STYLE_BG_COLOR, 0); // 删除通过本地样式(私有样式)设置的背景色
printf("LV_EVENT_CLICKED\n");
break;
default:
//printf("NONE\n");
break;
}
}
void lv_100ask_demo_course_2_2_6(void)
{
/* 创建一个基础对象 obj1 */
lv_obj_t * obj1 = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj1, 450, 250);
lv_obj_center(obj1); // 将对象与其父对象的中心对齐,这里的父对象是屏幕:lv_scr_act()
/* 以 obj1 创建一个基础对象 obj2 */
lv_obj_t * obj2 = lv_obj_create(obj1);
lv_obj_set_size(obj2, 400, 200);
lv_obj_center(obj2); // 将对象与其父对象的中心对齐,这里的父对象是屏幕:obj1
lv_obj_add_flag(obj2, LV_OBJ_FLAG_EVENT_BUBBLE); // 启用事件冒泡,将接收到的所有事件传播给父级
/* 以 obj2 创建一个基础对象 obj3 */
lv_obj_t * obj3 = lv_obj_create(obj2);
lv_obj_set_size(obj3, 350, 150);
lv_obj_center(obj3); // 将对象与其父对象的中心对齐,这里的父对象是屏幕:obj2
lv_obj_add_flag(obj3, LV_OBJ_FLAG_EVENT_BUBBLE); // 启用事件冒泡,将接收到的所有事件传播给父级
/* 以 obj3 创建一个基础对象 obj4 */
lv_obj_t * obj4 = lv_obj_create(obj3);
lv_obj_set_size(obj4, 300, 100);
lv_obj_center(obj4); // 将对象与其父对象的中心对齐,这里的父对象是屏幕:obj3
lv_obj_add_flag(obj4, LV_OBJ_FLAG_EVENT_BUBBLE); // 启用事件冒泡,将接收到的所有事件传播给父级
/* 以屏幕为父类,创建一个label部件(对象) */
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "test"); // 设置label展示的文字
lv_obj_align_to(label, obj1, LV_ALIGN_OUT_TOP_MID, 0, 0); // 将label相对于obj1对齐
// 将给obj1添加事件回调函数,所有的事件类型都能触发该回调函数
lv_obj_add_event_cb(obj1, my_event_cb, LV_EVENT_ALL, label);
}
#endif // 1
2.标签组件(lv_lable)
标签是用来显示文本的组件。
·创建标签:lv_obj_t * label = lv_label_create(parent);
代码:
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
·设置显示文本
直接设置要显示的文本:
lv_label_set_text(label, "New text");
格式化给定要显示的文本:
lv_label_set_text_fmt(label, “%s: %d”, “Value”, 15);
文本不存储在动态内存中,而是直接使用给定的缓冲区:
lv_label_set_text_static(label, "New text");
要在label换行非常简单,像printf函数那样使用 \n 即可:
lv_label_set_text(label, " line1\nline2\n\nline4 ");
案例1:直接设置要显示的文本
//方法一:
char * text = "www.100ask.net"; // 要显示的文字
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
/* 展示文字 */
/* 设置要显示的文本,函数中会另外开辟和给定的字符串大小的空间存放字符串(常用) */
lv_label_set_text(label, text);
//方法二:
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
/* 展示文字 */
/* 设置要显示的文本,函数中会另外开辟和给定的字符串大小的空间存放字符串(常用) */
lv_label_set_text(label, "www.100ask.net"); // 也可以这样使用
案例2:格式化给定要显示的文本
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
/* 展示文字 */
/* 设置要显示的文本,函数中会另外开辟和给定的字符串大小的空间存放字符串(常用) */
lv_label_set_text(label, "www.100ask.net"); // 也可以这样使用
/* 设置要显示的文字,也会另外开辟空间存放字符串,但是可以像 printf 格式化字符串 */
lv_label_set_text_fmt(label, "%s: %d", "Value", 15);
案例3:文本不存储在动态内存中,而是直接使用给定的缓冲区
//方法一:
char * text = "www.100ask.net"; // 要显示的文字
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
/* 设置要显示的文字,直接使用给定的缓冲区(不常用) */
lv_label_set_text_static(label, text);
//方法二:
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
/* 设置要显示的文字,直接使用给定的缓冲区(不常用) */
lv_label_set_text_static(label, "www.100ask.net"); // 也可以这样使用
案例4:要在label换行非常简单,像printf函数那样使用 \n 即可
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, " line1\nline2\n\nline4 ");
·设置文本大小
默认情况标签的大小会自动拓展成和文本一样的大小(LV_SIZE_CONTENT);
文本的宽度高度设置调整:
调整语句格式:lv_label_set_long_mode(label, LV_LABEL_LONG_...) ;
还可以向以前那样设置对象的宽高:
lv_obj_set_size( ,); lv_obj_set_width( ,); lv_obj_set_height( ,);
调整如下几种模式:
LV_LABEL_LONG_WRAP 如果有多个换行,并且如果高度为LV_SIZE_CONTENT,那么高度会根据文本换行被自动扩展;否则文本将被剪掉。(默认设置)
LV_LABEL_LONG_DOT 如果文本太长,就保持大小并在末尾写3个点 .
LV_LABEL_LONG_SCROLL 如果文本比标签宽(太长),则可以水平来回滚动显示它。如果它很高(多个\n换行),可以垂直滚动。只滚动一个方向,水平滚动的优先级更高。
LV_LABEL_LONG_SCROLL_CIRCULAR 如果文本比标签宽,则水平滚动它。如果它更高,就垂直滚动。只滚动一个方向,水平滚动的优先级更高。
LV_LABEL_LONG_CLIP 剪掉超出标签范围外的文本部分。
案例1:LV_LABEL_LONG_WRAP
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象
lv_obj_set_width(label,50);
lv_obj_set_height(label,50);
lv_obj_center(label);
lv_label_set_text(label, "tybyukkkkkkcsdvsgsgsgsdgsdgknyut");
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
案例2:LV_LABEL_LONG_DOT
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象)
lv_obj_set_width(label,50);
lv_obj_set_height(label,50);
lv_obj_center(label);
lv_label_set_text(label, "tybyukkkkkkcsdvsgsgsgsdgsdgknyut");
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
案例3:LV_LABEL_LONG_SCROLL
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
lv_obj_set_width(label,50);
lv_obj_set_height(label,50);
lv_obj_center(label);
lv_label_set_text(label, "tybyukkkkkkcsdvsgsgsgsdgsdgknyut");
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL);
案例4:LV_LABEL_LONG_SCROLL_CIRCULAR
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
lv_obj_set_width(label,50);
lv_obj_set_height(label,50);
lv_obj_center(label);
lv_label_set_text(label, "tybyukkkkkkcsdvsgsgsgsdgsdgknyut");
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR );
案例5: LV_LABEL_LONG_CLIP
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
lv_obj_set_width(label,50);
lv_obj_set_height(label,50);
lv_obj_center(label);
lv_label_set_text(label, "tybyukkkkkkcsdvsgsgsgsdgsdgknyut");
lv_label_set_long_mode(label, LV_LABEL_LONG_CLIP );
·设置文本颜色
给我们的要显示的文本重新着色,可以通过样式来上色:
例如:lv_style_set_text_color(&style_obj, lv_color_hex(0xf7b37b));
lv_obj_set_style_text_color(label, lv_color_hex(0xf7b37b), 0);
让文本某些部分重新着色:
例如: lv_label_set_recolor(label1, true);
·文本选择
如果在 lv_conf.h 中打开了LV_LABEL_TEXT_SELECTION (默认开启),就可以选择部分文本了。这个和我们在PC用鼠标选中文本类似,但是这个效果只能在文本框(lv_textarea)中实现。Label只能事先手动选择指定范围的文本:
lv_label_set_text_sel_start(label, 1);
lv_label_set_text_sel_end(label, 6);
注意,这里的第一个字符从1开始算,而不是0。
·显示图标
用法很简单(LV_SYMBOL_...):
lv_label_set_text(my_label, LV_SYMBOL_OK); // 直接显示图标
lv_label_set_text(my_label, LV_SYMBOL_OK “Apply”); // 图标与字符串一起使用
lv_label_set_text(my_label, LV_SYMBOL_OK LV_SYMBOL_WIFI LV_SYMBOL_PLAY); // 多个图标一起使用
·事件处理
Label默认不接收输入事件,如果我们想设置输入类型的样式或者事件会无法生效,就需要打开 LV_OBJ_FLAG_CLICKABLE
示例: lv_obj_add_flag(label, LV_OBJ_FLAG_CLICKABLE); // 使输入设备可点击对象
·显示中文
·LVGL内置有一个中文字库CJK字库。字库在lv_conf.h中定义为LV_FONT_SIMSUN_16_CJK
·要在lvgl中使用显示自己的中文字库,需要:字体文件和字体转换器
在lvgl内置的中文库CJK字库中只有常见的1000个汉字,而字体文件我们可以用开源的字体或自 己制作,准备好相应字体文件后使用字体转换器转换成可 以在lvgl上使用的字体格式:
开源字体获取页面:显示中文 — 百问网LVGL中文教程文档 文档
·准备好字体文件后再通过lvgl官方的字体转换器,提取想要的字体
LVGL官方在线字体转换器页面:https://lvgl.io/tools/fontconverter
案例1:调用内置中文字库
首先,我们要在lv_conf.h将LV_FONT_SIMSUN_16_CJK置1打开。
接着调用代码即可:
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(label, &lv_font_simsun_16_cjk, 0); // 使用内置的 cjk 字库
lv_label_set_text(label, "百问网嵌入式abcd123.,<>';'[]ABCD"); // 显示文字(注意当前文件文本的编码格式,要使用 utf-8 编码)
注意:
案例2:转换外部字体并调用外部字体
我们先去开源字体获取页面获取字体:显示中文 — 百问网LVGL中文教程文档 文档
下载后得到文件
然后我们打开字体转换器链接:Online font converter - TTF or WOFF fonts to C array | LVGLhttps://lvgl.io/tools/fontconverter
Online font converter - TTF or WOFF fonts to C array | LVGL
接着我们将转换好的文件放进所在的文件夹内
调用代码:
LV_FONT_DECLARE(font_source_han_sans_bold_20); //外部声明
/* 创建一个基础对象 label */
lv_obj_t * label = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
lv_obj_set_style_text_font(label, &font_source_han_sans_bold_20, 0); // 使用自定义的字库
lv_label_set_text(label, "百问网嵌入式")//显示文字(注意当前文件文本的编码格式要使用utf-8编码)
注意:一定要调用外部声明
在你的项目应用程序的 C 文件中,将字体声明为:
extern lv_font_t my_font_name;
或: LV_FONT_DECLARE(my_font_name);
3.按钮组件(lv_btn)
按钮组件和基础对象是最像得一个组件,他们的区别在于:
·不可滚动
·添加到默认组(可修改为给其他输入设备控制用,比如按键)
·高度和宽度默认为 LV_SIZE_CONTENT
按钮组件
创建按键的函数格式:lv_obj_t * btn = lv_btn_create(parent);
代码:
/* 创建一个btn部件(对象) */
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 创建一个btn部件(对象),他的父对象是活动屏幕对象
按钮的部分和样式
按钮的组成部分和基础对象一样,只有一个空空的盒子(盒子模型)。
按钮的部分(Part)只有下面这个: LV_PART_MAIN 按钮的背景
代码:
/* 创建一个btn部件(对象) */
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 创建一个btn部件(对象),他的父对象是活动屏幕对象
// 修改按钮部件(对象)矩形背景部分的样式
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1e1e1e), LV_PART_MAIN | LV_STATE_PRESSED);
按钮样式的修改
lv_obj_set_style_<property_name>(obj, <value>, <selector>); // 本地(私有)样式 lv_obj_add_style(obj, &style, <selector>) // 普通(共享)样式
所以修改样式的时候我们修改部分(Part)的时候只有 LV_PART_MAIN 可以生效;状态部分和前面说的样式状态(States)都可以用
按钮添加事件
事件可以像前面说的事件处理部分使用,有个细节我们是需要注意的, 如果打开了 LV_OBJ_FLAG_CHECKABLE ,当对象被点击时有选中切换(Toggle)状态的效果,并且可以在 LV_EVENT_VALUE_CHANGED 事件类型中处理事件,
比如: lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
/* 创建一个btn部件(对象) */
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 创建一个btn部件(对象),他的父对象是活动屏幕对象
// 修改按钮部件(对象)矩形背景部分的样式
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1e1e1e), LV_PART_MAIN | LV_STATE_PRESSED);
// 打开了 LV_OBJ_FLAG_CHECKABLE ,当对象被点击时有选中切换(Toggle)状态的效果
// 其触发的是 LV_EVENT_VALUE_CHANGED 事件类型
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
按钮按键控制
总的来看,按钮是用来按的(单击、按下或不按下),所以我们用物理按键控制的时候只需要 LV_KEY_ENTER 就可以控制按钮, 并且 LV_KEY_ENTER 可以触发 LV_EVENT_PRESSED/PRESSING/RELEASED 等事件类型。
static void btn_toggle_event_cb(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e); // 获取触发事件的部件(对象)
lv_event_code_t code = lv_event_get_code(e); // 获取当前部件(对象)触发的事件代码
switch(code){
case LV_EVENT_VALUE_CHANGED:
printf("LV_EVENT_VALUE_CHANGED\n");
break;
default:
//printf("NONE\n");
break;
}
}
void lv_100ask_demo_course_3_2_1(void)
{
/* 创建一个btn部件(对象) */
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 创建一个btn部件(对象),他的父对象是活动屏幕对象
// 修改按钮部件(对象)矩形背景部分的样式
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1e1e1e), LV_PART_MAIN | LV_STATE_PRESSED);
// 打开了 LV_OBJ_FLAG_CHECKABLE ,当对象被点击时有选中切换(Toggle)状态的效果
// 其触发的是 LV_EVENT_VALUE_CHANGED 事件类型
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
// 处理 LV_EVENT_VALUE_CHANGED 事件类型示例
lv_obj_add_event_cb(btn, btn_toggle_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
}
总的来看,按钮是用来按的(单击、按下或不按下),所以我们用物理按键控制的时候只需要 LV_KEY_ENTER 就可以控制按钮, 并且 LV_KEY_ENTER 可以触发 LV_EVENT_PRESSED/PRESSING/RELEASED 等事件类型。