对象的概念
- LVGL是采用面向对象的编程思想(OOP),她的基本构造块(类)是对象的实例。每个部件都是一个对象的实例,如Button、Label等。
- 在LVGL中,lv_obj_t(类)定义了部件的抽象特点,其定义包含了数据的形式以及对数据的操作。部件子类比原本的类(父类或基类)要更加具体化,子类会继承父类的属性和行为。
- 在LVGL中所有的对象都在lv_obj_t这个结构体的基础上进行演变,所以我们就看到了各种不一样的部件。计算是一样的部件,继承父类后演变出来的对象形态、风格样式也是不一样的。
- 因为lvgl是使用C语言编写的,因此lv_obj_t只能通过结构体来表示,并不是一个实例化后的类。因此我们需要先实例化出一个父类,所有的部件都是继承这个父类的,这个父类就是基础对象(lv_obj)。
LVGL中的基础对象
- 在lvgl中所有对象都有一个共同的父类,这个父类就是基础对象。所有的类都是直接或者间接继承这个基础对象的。
- lvgl中的基础对象就是lv_obj,这个基础对象有2个作用:一是作为所有部件的父类,二是作为屏幕显示的对象(屏幕是没有父类的基础对象)。
- 在lvgl中获取基础对象的方式是
lv_scr_act()
。这个基础对象是在main函数中的lv_init()
被初始化的。 - 一般一个屏幕对象分为3层:用户层、顶层、系统层。
- 用户层(
lv_scr_act(void)
)一般我们操作的都是用户层 - 顶层(
lv_layer_top(void)
)是用来做出一些响应的,如按键后弹出一个对话框。 - 系统层(
lv_layer_sys(void)
)一般是不受任何界面对象限制的,如鼠标可以在界面任意位置移动,鼠标就是在系统层。
- 用户层(
屏幕对象的创建过程
- 在
lv_init()
中的_lv_ll_init(&LV_GC_ROOT(_lv_disp_ll), sizeof(lv_disp_t));
初始化显示器的链表 - 在lv_disp_drv_register中的_lv_ll_ins_head(&LV_GC_ROOT(_lv_disp_ll);注册显示器到链表,并且在显示器上创建一个默认屏幕disp->act_scr=lv_obj_create(NULL);
基础对象的大小(size)
- 调整基础对象的大小
- 设置宽度:
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)
- 获取宽度:
基础对象的位置(position)
-
在lvgl中位置是相对屏幕的位置。
-
在现实生活中,我们所说的原点,一般是指“笛卡尔坐标系”,也叫做直角坐标系,就是我们数学中使用的坐标系,它的原点在左下角,y轴自下而上,x轴自左向右。而在屏幕中我们使用的是“lcd坐标系”,它的原点是在左上角,y轴是自上而下的,x轴自左向右。
-
只要确定了原点,我们就可以按照坐标系,确定部件的位置。
-
设置基础对象的位置
(注意:设置的x或者y支持设置负值,显示效果会是超过屏幕的边界,只显示可显示的区域)- 设置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)
- 设置x轴位置:
-
设置基础对象的对齐(Alignment)
如果每个部件都需要准确计算具体的坐标位置,有时间会太麻烦。lvgl提供了对齐的表示方式- 参照父对象对齐:
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)
基础对象的盒子模型(border-box)
lvgl遵循CSS的border-box模型,对象的盒子由以下部分组成:
- 边界(dounding):元素的宽度和高度围起来的区域(整个盒子)
- 边框(border):边框有大小和颜色等属性(相当于盒子的厚度和它的颜色)
- 填充(padding):对象两侧与其子对象之间的空间(盒子的填充物)
- 内容(content):如果边界框
- 轮廓(outline):lvgl中没有外边距(margin)的概念(盒子与其他盒子之间的距离),取而代之的是轮廓outline。它是绘制于元素(盒子)周围的一条线,不占据内部空间,位于边框边缘外围。
基础对象的样式(style)
普通样式
样式就是用名称保存下来的,对修饰对象进行修饰所使用的一组修饰参数
- 样式初始化:要使用样式,必须先初始化样式。在lvgl中,样式是储存在lv_style_t变量中的。样式变量应该是静态的、全局或动态分配的。不能是局部变量,因为函数结束时会被销毁。
static lv_style_t style_obj;
lv_style_init(&style_obj);
- 设置样式的属性:设置样式属性的函数接口的格式:
lv_style_set_<property_name>(&style, <value>)
,例如:
/* 设置背景色 */
lv_style_set_bg_color(&style_obj,lv_color_hex(0x000000);
/* 设置背景透明度 */
lv_style_set_bg_opa(&style_obj, LV_OPA_50);
- 添加样式到对象:当我们初始化并设置好一个样式后,就可以将其应用到对象上。接口函数是
lv_obj_add_style(obj, &style, <selector>)
,selector是添加样式的部分和状态的OR-ed值(不能是互斥,否则就是清除标志,没法合并),示例:
/* 默认(常用) */
lv_obj_add_style(obj, &style_obj, 0);
/* 在对象被按下时应用样式 */
lv_obj_add_style(obj, &style_obj, LV_STATE_PRESSED);
- 我们可以给一个控件的不同部分别分设置样式,也可以将同一个样式设置给不同的对象。
- 我们可以对控件整体设置样式,也支持对部分进行设置。
对象可以有部分(parts),他们也可以有自己的样式,LVGL中存在以下预定义的部分:
名称 | 描述 |
---|---|
LV_PART_MAIN | 类似矩形的背景 |
LV_PART_SCROLLBAR | 滚动条 |
LV_PART_INDICATOR | 指示,例如用于滑块、条、开关或者复选框的勾选框 |
LV_PART_KNOB | 像手柄一样可以抓取的调整值 |
LV_PART_SELECTED | 表示当前选择的选项和部分 |
LV_PART_ITEMS | 如果小部件 |
本地样式
- 除了普通样式,对象还可以存储本地样式
- 本地样式和普通样式类似,但是不能和其他对象共享。
- 如果使用本地样式,将自动分配局部样式,并在对象删除时释放。
- 本地样式对于向对象添加本地自定义很有用。
- 如果同时定义了本地样式和普通样式,本地样式的优先级更高。
本地样式的接口函数:lv_obj_set_style_local_<property_name>(obj, <value>, <selector>);
示例:
/* 设置背景色 */
lv_obj_set_style_bg_color(obj,lv_color_hex(0xffff),0);
/* 设置背景透明度 */
lv_obj_set_style_bg_opa(obj,LV_OPA_50,0);
样式继承
如果父类设置了样式的属性,一个子类继承父类后,也继承了父类的样式。
基础对象的事件(events)
添加事件
lv_obj_add_event_cb(obj, event_cb, event_code, user_data);
event_cb是触发事件的回调函数
event_code(事件类型,指的是什么操作下会执行回调函数),可以在这里指明回调的操作,也可以在这里指明LV_EVENT_ALL所有事件,在回调函数里根据事件处理相应的内容。
一般的事件类型可以分为以下这几类:
输入设备事件(Input device events)
绘图事件(Drawing events)
其他事件(Special events)
特殊事件(Other events)
自定义事件(Custom events)
示例
static void btn_event_cb(lv_event_t* e)
{
/* 通过code获取触发事件的类型 */
lv_event_code_t code = lv_event_get_code(e);
/* 在回调函数里可以通过target,获取触发这个事件的对象 */
lv_obj_t* btn = lv_event_get_target(e);
/* 通过对象获取参数 */
lv_obj_t* obj = (lv_obj_t*)lv_event_get_user_data(e);
if (code == LV_EVENT_CLICKED)
{
static uint8_t cnt = 0;
cnt++;
/* 获取触发源对象的第一个子对象 */
lv_obj_t* label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, "Button:%d", cnt);
lv_obj_set_style_bg_color(obj, lv_color_hex(0x000000), 0);
}
}
void login_page(void)
{
lv_obj_t* btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 10, 10);
lv_obj_set_size(btn, 120, 50);
printf("obj width is %d\r\n", lv_obj_get_width(btn));
lv_obj_set_align(btn, LV_ALIGN_CENTER);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 100);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, "Button");
lv_obj_center(label);
lv_obj_align_to(label, btn, LV_ALIGN_CENTER, 5, 5);
lv_obj_t* obj = lv_obj_create(lv_scr_act());
lv_obj_center(obj);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, obj);
}
发送事件
lv_event_send(obj, event_cb, event_code, user_data);
删除事件
删除事件,可以使用对象+回调函数确定删除的对象,也可以在添加事件时,保存添加返回的指针对象,用这个指针对象来删除事件。
lv_obj_remove_event_cb(obj, event_cb);
/* event_dsc是lv_obj_add_event_cb返回的指针 */
lv_obj_remove_event_dsc(obj, event_dsc);
事件回调函数的lv_event_t参数
事件回调只有一个参数对象,通过这个参数对象,我们可以获取很多和对象关联的对象:
- 获取触发的事件源:
lv_event_code_t code = lv_event_get_code(e);
- 获取触发事件的对象:
lv_obj_t* targe = lv_event_get_target(e);
- 获取最初触发事件的对象(事件冒泡)
lv_obj_t* target = lv_event_get_current_target(e);
- 获取事件传递的用户数据:
lv_event_get_user_data(e);/* 获取使用lv_obj_add_event_cb()传递的用户数据 */
lv_event_get_param(e);/* 获取使用lv_event_send()传递的用户数据 */
事件冒泡
如果对象启用了lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE),该对象的所有事件将会发送到该对象的父级。如果父级也启用了LV_OBJ_FLAG_EVENT_BUBBLE,那么事件继续发送到他的父级,以此类推。
lv_event_get_target(e);获取触发事件的当前对象。
lv_event_get_current_target(e);获取触发事件的冒泡的父对象。(如果父对象也启用,层层递推,拿到的是最顶级的父对象)。