学习 lvgl 的理解总结,如有错误,麻烦各位大佬帮忙指正
目录
什么是 lvgl ?
lvgl 其实就是各种图形部件 Widget(如滑块、滚轮、按钮等)的集合库(GUI 库)。每个部件都是通过 API 函数创建(函数格式一般都是 lv_xxx_create( ),xxx 是部件名)。lv_obj_t 是部件类型,是结构体进行 typedef 后的重命名(可用来创建结构体或结构体指针,这个结构体内部具备所有部件的属性),因此,所有部件都具备一些共同属性,例如大小、颜色、位置、样式这些,只是不同部件其具体的参数可能不同
lvgl 采用了一定的面向对象编程的设计思想,我们操控的都是一个个别人事先为我们准备好编写好的部件(对象),如果想要使用部件,就得先创建部件需要调用部件创建函数(实例化),不像 C 是面向过程的编程,不管做什么都是从零开始(想使用部件也得从零开始编写出一个部件)。所有的对象都是在 lv_obj_t 这个结构体的基础上进行演变的,诞生各种不一样的部件
关于 lvgl 的屏幕
屏幕在 lvgl 中,是没有父类的基础对象,是 lvgl 初始化时就默认创建的部件,其他所有部件都是屏幕的衍生物(其他部件都继承于屏幕,屏幕是所有部件的父对象,准确说,屏幕是其他所有部件的最初父对象),如下:
Q:为什么说其他部件均继承于屏幕,或其他部件是屏幕的衍生物?
A:创建部件时,需要调用部件创建函数,部件创建函数有一个参数是 parent,即父对象,就是指定一个对象作为父对象,在父对象上创建生成一个部件,父对象是新部件的容器、是新部件的活动范围,没有父对象的话,新部件就无法出现(当然基础对象屏幕除外),如下:
lv_obj_t * obj1 = lv_obj_create(lv_scr_act()); //创建部件 obj1,父对象是屏幕
lv_obj_set_size(obj1, LV_PCT(40), LV_PCT(40)); //设置 obj1 的宽度、高度
lv_obj_align(obj1, LV_ALIGN_CENTER, 0, 0); //以父对象作为参照物进行居中
lv_obj_t * obj2 = lv_obj_create(obj1); //创建部件 obj2,父对象是 obj1
lv_obj_set_size(obj2, LV_PCT(20), LV_PCT(20)); //设置 obj2 的宽度、高度
lv_obj_align(obj2, LV_ALIGN_CENTER, 0, 0); //以父对象作为参照物进行居中
可以看到,obj1 是在屏幕上创建的,位于屏幕内,也只能在屏幕内活动。obj2 是在 obj1 上创建的,位于 obj1 内,也只能在 obj1 内活动。
除屏幕外的所有部件,第一个创建的部件永远是以屏幕作为父对象才能被创建的。
部件的基本属性(所有部件都具备的属性)
一、大小
设置部件的宽度、高度的函数
lv_obj_set_width (obj, new_width) | 设置 obj 部件的宽度 |
lv_obj_set_height (obj, new_height) | 设置 obj 部件的高度 |
lv_obj_set_size (obj, new_ width, new_ height) | 设置 obj 部件的宽度和高度 |
获取部件的宽度、高度的函数
lv_obj_get_width (obj) | 获取 obj 部件的宽度 |
lv_obj_get_height(obj) | 获取 obj 部件的高度 |
二、位置
我们常见的坐标系是“直角坐标系”,这是我们学习数学时都在使用的坐标系。LVGL 屏幕的坐标系和我们熟悉的坐标系不一样,LVGL 的坐标系称为“LCD 坐标系”,他的原点位置和直角坐标系的不一样,原点位置在屏幕左上角,两者区别如下图
有了 LCD 坐标系,确认了原点位置,我们就可以此来设置部件的位置
设置 x y 轴位置函数
lv_obj_set_x (obj, new_x) | 设置 obj 部件 x 轴方向的坐标位置 |
lv_obj_set_y (obj, new_y) | 设置 obj 部件 y 轴方向的坐标位置 |
lv_obj_set_pos (obj, new_x, new_y) | 同时设置 obj 部件 x、y 坐标位置 |
对齐函数(有不同的对齐模式)
lv_obj_set_align (obj, LV_ALIGN_...) | 以父对象为参照物向中间对齐 |
lv_obj_align (obj, LV_ALIGN_..., x, y) | 以父对象为参照物向中间对齐,并以对齐后的位置为原点再偏移 (x,y) |
lv_obj_align_to (obj_to_align, obj_referece, LV_ALIGN_..., x, y) | 以另一个对象 (无父子关系) 为参照物进行对齐,对齐后再偏移 (x,y) |
对齐类型(即函数内的 LV_ALIGN_... 参数,根据 “...” 的不同,obj 会跑到类型指定位置)
三、样式
样式,就是部件的外表属性,我们可以设置部件的样式,来达到优化显示界面、实现用户交互
优化显示界面:
如下图按键中,如果发生重大事故,需要急停,明显右边的红色按钮更加明显好按。
实现用户交互:
就是你鼠标放到部件上后有反馈,部件有发生变化,让用户知道自己点下去了。
1、有哪些样式
大小(Size)、位置(Position)、背景(Background)、轮廓(Outline)、边框(Border)、阴影(Shadow)、其他(Others)
2、如何给部件添加样式
1)普通样式(公共样式)
公有样式,创建后就放在那里,可以一直被调用,其他部件想用就调用样式添加函数即可。所有部件都能调用 lv_obj_add_style () 来添加样式。其中第二个参数作用:当部件处于什么状态时触发添加的样式,例如鼠标放在部件上后生效,或者是点击后生效,可选参数在下面会列举
添加公有样式的流程
1、定义样式变量:static lv_style_t style;(style名称可以用户自定义)
2、初始化样式:lv_style_init( &style );3、添加公共样式:lv_style_set_xxx( &style, <value> );(xxx是样式属性名,如下图列举一部分xxx)
4、给部件添加样式:lv_obj_add_style( obj, &style, LV_STATE_zzz );(zzz是可选参数名)
程序示例(改变部件背景颜色)如下:
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_add_style(obj, & style, LV_STATE_DEFAULT); /* 给部件添加样式 *
2)本地样式(私有样式)示例如下(改变部件背景颜色)
除了普通样式,还有本地样式,本地样式跟普通样式区别在于:本地样式不能跟其他对象间共享。如果使用本地样式,将自动分配局部样式,并在删除对象时释放。
添加本地样式的流程:
lv_obj_set_style_xxx (obj, <value>, LV_STATE_zzz );(zzz是可选参数名)
程序示例(改变部件背景颜色)如下:
lv_obj_t * obj = lv_obj_create(lv_scr_act()); /* 创建一个部件 */
lv_obj_set_style_bg_color(obj, lv_color_hex(0xf4b183),LV_STATE_DEFAULT);/* 给部件添加样式 */
3)参数 LV_STATE_zzz :决定了样式什么时候生效
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, /* 禁用状态 */
…
};
4)边框和轮廓的区别
边框(border)是什么?
举例如下:
a、首先先创建一个部件:
b、给部件添加样式边框颜色
c、修改部件的样式边框的宽度
d、修改样式边框的透明度(值越小越透明,可设置值的范围 0~250)
轮廓(outline)是什么?
举例如下:(轮廓是在边框的外边)
3、如何单独设置部件中某个部分的样式
例如,滑块部件是由三个部分组成,如下图
lvgl 官方提供了下方枚举,提供给我们设置部件中某个部分的样式:
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, /* 光标 */
};
使用举例:
a、首先先创建一个滑块
b、给滑块部件添加背景颜色,注意:默认修改的是部件主体部分的颜色
c、修改滑块部件的手柄和指示器的背景颜色
四、事件
事件,就是我们触碰部件后,可以触发回调事件,执行事件回调函数,事件回调函数是用户自己编写的,有点类似中断,如下
想要触碰部件后能进入触发回调事件,需要先给部件加上事件,添加事件用到如下 API 函数
添加事件函数 | lv_obj_add_event_cb (obj, event_cd, event_code, user_data); |
参数 | 1、obj:要添加事件的部件
2、event_cd:事件回调函数,函数名自定义
3、event_code:事件类型(可以分为五类,如下)
4、user_data:用不到就设置为 NULL |
删除事件函数 | lv_obj_remove_event_cb (obj, event_cb); |
1、事件类型
事件类型分为 5 大类:
输入设备事件、绘图事件、自定义事件、特别活动、其他活动。
所有对象,如按钮、标签、滑块等等,无论事件类型如何,都会接收输入设备事件、绘图事件、其他事件。但特殊事件是特定于特定的小部件类型。自定义事件是由用户添加,不由 LVGL 发送。
具体详细参考 LVGL 官方文档资料:
如下是输入设备的部分事件类型:
2、事件回调函数
格式:static void xxx ( lv_event_t *e ) { } //xxx 是函数名,程序员自己任意定义
(1)不同的事件类型共用同一个事件回调函数时,不清楚是哪个事件类型触发了事件回调函数,这时该怎么解决才能区分是哪个事件类型触发的?解决方法如下,在事件回调函数内调用获取事件类型的函数,如下:
static void xxx( lv_event_t *e )
{
lv_event_code_t code = lv_event_get_code(e); /* 第一步:获取事件类型 */
if ( code == LV_EVENT_CLICKED ) /* 第二步:判断事件类型 */
{
printf(“事件类型: 按下后释放\r\n”); /* 第三步:执行相应操作 */
}
else if ( code == LV_EVENT_LONG_PRESSED)
{
printf(“事件类型:按下(长按)\r\n”);
}
}
示例:
(2)不同的部件共用一个事件回调函数时,不清楚是哪个部件调用了事件回调函数,这时怎么处理才能知道触发事件回调时是哪个部件在触发?解决方法如下,在事件回调函数中调用获取触发事件部件的函数,如下:
static void xxx( lv_event_t *e )
{
lv_obj_t *target = lv_event_get_target(e); /* 第一步:获取触发事件的部件 */
if ( target == parent_obj ) /* 第二步:判断触发事件的部件 */
{
printf(“父对象触发事件 \r\n”); /* 第三步:执行相应操作 */
}
else if ( target == child_obj )
{
printf(“子对象触发事件 \r\n”);
}
}
示例: