目录
一、对象简介
二、基础对象简介
1、概念(lv_obj)
屏幕是没有父类的基础对象
屏幕对象的创建过程
LVGL的三层屏幕
2、大小(size)
相关函数
代码示例
3、位置(position)
LVGL屏幕的原点
屏幕(部件区域)的表示
相关函数
LV_ALIGN_...参数解析
代码示例
4、盒子模型(border-box)
CSS 盒子模型
LVGL对象的盒子模型
相关代码
5、样式(styles)
概念
初始化样式
设置样式属性
添加(应用)样式到对象
获取样式属性
删除样式
查看样式属性
背景部分的属性
样式状态
样式部分
代码示例(1)
代码示例(2)
本地样式
代码示例(3)
样式继承
过度特效
样式主题
6、事件(events)
什么是事件?
相关函数
事件类型(event_code)
代码示例(1)
代码示例(2)
代码示例(3)
事件回调函数的 lv_event_t 参数
事件冒泡
代码示例(4)
一、对象简介
LVGL采用面向对象的编程思想(OOP),它的基本构造块(类)是对象(实例),也就是我们所说的部件(Widgets)就是一个个部件,比如button、label、image等等。
lv_obj_t (类)定义了部件的抽象特点,其定义包含了数据的形式以及对数据的操作。部件(子类)比原本的类(称为父类或基类)要更加具体化,子类会继承父类的属性和行为。
在LVGL中,所有的对象都在 lv_obj_t 这个结构体的基础上进行演变,所以我们就看到了各种不一样的部件,就算是一样的部件,继承基础父类(基类)之后演变出来对象(实例)的形态或风格样式都不一样 (就像狗有不一样的品种,不一样的品种就有不一样的体型、外观;就算是同一品种的狗它的毛色、性格都会不一样等等)。 比如不同的部件就有不一样的效果,按钮和下拉列表。 比如同样创建出来的按钮部件,你可以是红色的,我的是蓝色的,他的是有阴影效果并且有被按下时不同的互动效果等等。这样我们看到就是多姿多彩的用户界面了,每个人写出来的界面可能都不一样! 由于lvgl使用c语言编写,因此 lv_obj_t 只能通过结构体来表示,它并不是一个实例化后的类,因此我们需要先实例化出一个父类(基类),其他所有的部件(对象)都继承自这个父类(基类),也就是我们下面说到的基础对象(lv_obj)。
二、基础对象简介
1、概念(lv_obj)
基础对象实现了屏幕上小部件的基本属性,例如:
- 座标
- 父对象
- 子对象
- 主要风格
- 属性,例如点击启用、拖动启用等。
在面向对象的思想中,它是继承 LVGL 中所有其他对象的基类。这尤其有助于减少代码重复。
基础对象的功能也可以与其他小部件一起使用。例如 lv_obj_set_width(slider,100)
基础对象可以直接用作简单的小部件。然后就是矩形。
lv_obj_t * obj = lv_obj_create(lv_scr_act());//创建一个对象
lv_obj_t * label = lv_label_create(obj);//创建一个基础对象
//lv_obj_create函数展开
lv_obj_t * lv_obj_create(lv_obj_t * parent)
{
LV_LOG_INFO("begin");
lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
return obj;
}
//lv_label_create函数展开
lv_obj_t * lv_label_create(lv_obj_t * parent)
{
LV_LOG_INFO("begin");
lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
return obj;
}
由两个函数的展开可以看见传入参数全是父类,此时 lv_label_create 函数的参数有两种形式,一种是直接用 lv_obj_create 函数创建的基类 lv_scr_act(),即 lv_obj_t * label = lv_label_create(lv_scr_act()); ,另一种是通过基类创建出来的对象给另一个对象或部件当父类用,即上述代码格式。
屏幕是没有父类的基础对象
lv_win32_init(hInstance, SW_SHOWNORMAL, 1024, 600, NULL);//主函数中的屏幕初始化函数
static lv_disp_drv_t display_driver;//屏幕初始化函数展开后第338行
lv_disp_drv_init(&display_driver);
display_driver.hor_res = hor_res;//水平高度
display_driver.ver_res = ver_res;//垂直高度
display_driver.flush_cb = lv_win32_display_driver_flush_callback;
display_driver.draw_buf = &display_buffer;
display_driver.rounder_cb = lv_win32_display_driver_rounder_callback;
g_display = lv_disp_drv_register(&display_driver);//将上述结构体各项参数传入新函数
disp->act_scr = lv_obj_create(NULL); /*Create a default screen on the display 新函数展开后发现创建了一个参数为NULL的屏幕*/
disp->top_layer = lv_obj_create(NULL); /*Create top layer on the display*/
disp->sys_layer = lv_obj_create(NULL); /*Create sys layer on the display*/
//将 lv_obj_create 函数中的 lv_obj_class_create_obj 函数展开如下
if(parent == NULL) {//当父类为NULL时
LV_TRACE_OBJ_CREATE("creating a screen");
lv_disp_t * disp = lv_disp_get_default();
if(!disp) {
LV_LOG_WARN("No display created yet. No place to assign the new screen");
return NULL;
}
if(disp->screens == NULL) {
disp->screens = lv_mem_alloc(sizeof(lv_obj_t *));
disp->screens[0] = obj;
disp->screen_cnt = 1;
}
else {
disp->screen_cnt++;
disp->screens = lv_mem_realloc(disp->screens, sizeof(lv_obj_t *) * disp->screen_cnt);
disp->screens[disp->screen_cnt - 1] = obj;
}
/*Set coordinates to full screen size*/
obj->coords.x1 = 0;
obj->coords.y1 = 0;
obj->coords.x2 = lv_disp_get_hor_res(NULL) - 1;//水平高度
obj->coords.y2 = lv_disp_get_ver_res(NULL) - 1;//垂直高度
}
屏幕对象的创建过程
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); // 在显示器上创建一个默认屏幕 没有父类的基础对象
lv_obj_class_create_obj
obj->coords.x1 = 0;
obj->coords.y1 = 0;
obj->coords.x2 = lv_disp_get_hor_res(NULL) - 1; // 设置屏幕的水平宽度
obj->coords.y2 = lv_disp_get_ver_res(NULL) - 1; // 设置屏幕的垂直高度
LVGL的三层屏幕
lv_scr_act(void); // 活动屏幕 disp->act_scr
lv_layer_top (void); // 顶层 disp->top_layer
lv_layer_sys (void); // 系统层 disp->sys_layer
一般来说,我们作为用户只需要操作用户层(活动屏幕层);而顶层一般用来做一些特殊处理,比如按下某个按钮后会弹出一个对话框,对话框内的内容必须完成才能执行下一步;系统层主要表现在鼠标在屏幕上面可以任意滑动,一般不建议改动。
2、大小(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);
代码示例
void lv_100ask_demo_course_2_2_2(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
//lv_obj_set_width(obj, 300);
//lv_obj_set_height(obj, 500);
lv_obj_set_size(obj,300, 300);
printf("w:%d\n", lv_obj_get_width(obj));
printf("h:%d\n", lv_obj_get_height(obj));
}
不难发现的是这里的高度和宽度都显示为0,这个问题留到下面章节解决。
3、位置(position)
LVGL屏幕的原点
我们常见的坐标系是“笛卡尔坐标系”,或者叫“直角坐标系”。这种坐标系 我们很熟悉,因为我们学习数学的时候一般就是使用这种坐标系。
LVGL屏幕的坐标系和我们熟悉的坐标系不一样,LVGL的坐标系是我们 可以称为“LCD坐标系”,他的原点位置和直角坐标系的不一样。
确定了坐标系类型后我们就可以确定原点,确定了原点我们就可以设置、确定 LVGL部件(对象)的位置。 其实我们只需要记住一点: LVGL的原点位置在屏幕左上角。
屏幕(部件区域)的表示
相关函数
位置(Position)
设置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); // position
对齐(Alignment)
参照父对象对齐: 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);
LV_ALIGN_...参数解析
/** Alignments*/
enum {
LV_ALIGN_DEFAULT = 0,
LV_ALIGN_TOP_LEFT,
LV_ALIGN_TOP_MID,
LV_ALIGN_TOP_RIGHT,
LV_ALIGN_BOTTOM_LEFT,
LV_ALIGN_BOTTOM_MID,
LV_ALIGN_BOTTOM_RIGHT,
LV_ALIGN_LEFT_MID,
LV_ALIGN_RIGHT_MID,
LV_ALIGN_CENTER,
LV_ALIGN_OUT_TOP_LEFT,
LV_ALIGN_OUT_TOP_MID,
LV_ALIGN_OUT_TOP_RIGHT,
LV_ALIGN_OUT_BOTTOM_LEFT,
LV_ALIGN_OUT_BOTTOM_MID,
LV_ALIGN_OUT_BOTTOM_RIGHT,
LV_ALIGN_OUT_LEFT_TOP,
LV_ALIGN_OUT_LEFT_MID,
LV_ALIGN_OUT_LEFT_BOTTOM,
LV_ALIGN_OUT_RIGHT_TOP,
LV_ALIGN_OUT_RIGHT_MID,
LV_ALIGN_OUT_RIGHT_BOTTOM,
};
参数主要是枚举参数,各参数可以用如下图表示:
代码示例
void lv_100ask_demo_course_2_2_3(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act());
//lv_obj_set_x(obj, 200); //在x=200处建立原点
//lv_obj_set_y(obj, 200); //在y=200处建立原点
//lv_obj_set_pos(obj, 300, 300); //在x=300,y=300处建立原点
//lv_obj_set_align(obj, LV_ALIGN_CENTER); //在屏幕中心处建立原点
lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0);//在基于底部中间x=0,y=0处建立原点
lv_obj_t * label = lv_label_create(lv_scr_act()); //新建立一个对象
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_align_to(label, obj, LV_ALIGN_CENTER, 0, 0); //在参照上面obj对象坐标位置的中心x=0,y=0处写出 Hello, LVGL!
printf("x:%d\n", lv_obj_get_x(obj)); //打印x坐标的值
printf("y:%d\n", lv_obj_get_y(obj)); //打印y坐标的值
}
4、盒子模型(border-box)
CSS 盒子模型
网页设计中常听的属性名:内容(content)、内边距(padding)、边框(border)、外边距(margin), CSS盒子模式都具备这些属性。这些属性我们可以把它转移到我们日常生活中的盒子(箱子)上来理解,日常生活中所见的盒子也就是能装东西的一种箱子,也具有这些属性,所以叫它盒子模型。CSS盒子模型就是在网页设计中经常用到的CSS技术所使用的一种思维模型。
content就是盒子里装的东西,它有高度(height)和宽度(width),可以是图片,可以是文字或者小盒子嵌套,在现实中,内容不能大于盒子,内容大于盒子就会撑破盒子,在LVGL中盒子不会变化,会产生滚动条,我们可以滚动查看超出盒子的内容;但在css中,盒子有弹性的,顶多内容太大就会撑大盒子,但是不会损害盒子。
padding即是填充,就好像我们为了保证盒子里的东西不损坏,填充了一些东西,比如泡沫或者塑料薄膜,填充物有大有小,有软有硬,反应在网页中就是padding的大小了。
border就是再外一层的边框,因为边框有大小和颜色的属性,相当于盒子的厚度和它的颜色或者材料。
margin外边距,就是我们的盒子与其他的盒子或者其他东西的距离。假如有很多盒子,margin就是盒子之间直接的距离,可以通风,也美观同时方便取出。
LVGL对象的盒子模型
LVGL 遵循 CSS 的 border-box 模型。 对象的“盒子”由以下部分构成:
- 边界(bounding):元素的宽度/高度围起来的区域(整个盒子)。
- 边框(border):边框有大小和颜色等属性(相当于盒子的厚度和它的颜色)。
- 填充(padding):对象两侧与其子对象之间的空间(盒子的填充物)。
- 内容(content):如果边界框按边框宽度和填充的大小缩小,则显示其大小的内容区域(盒子实际装东西的区域)。
- 轮廓(outline) :LVGL中没有外边距(margin)的概念(盒子之间的距离),确认代之的是轮廓(outline)。它是绘制于元素(盒子)周围的一条线,它不占据空间,位于边框边缘的外围,可起到突出元素(盒子)的作用。在浏览器里,当鼠标点击或使用Tab键让一个选项或者一个图片获得焦点的时候,这个元素就会多了一个轮廓框围绕。轮廓(outline) 。
- LVGL的盒子模型是我们理解对象(部件)的组成,修改对象的样式,实现对对象的布局、处理对象排列等等的关键。
相关代码
void lv_100ask_demo_course_2_2_4(void)
{
lv_obj_t * obj1 = lv_obj_create(lv_scr_act());
lv_obj_align(obj1, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_border_width(obj1, 10, 0); //设置盒子的边框
lv_obj_set_style_outline_width(obj1, 10, 0); //设置盒子的轮廓
#if 0 //为0 下面函数无法实现
lv_obj_t * obj2 = lv_obj_create(lv_scr_act());
lv_obj_t * obj_out_top = lv_obj_create(lv_scr_act());
lv_obj_t * obj_out_bottom = lv_obj_create(lv_scr_act());
lv_obj_t * obj_out_left = lv_obj_create(lv_scr_act());
lv_obj_t * obj_out_right = lv_obj_create(lv_scr_act());
//lv_obj_set_style_outline_width(obj_out_left, 10, 0);
lv_obj_align(obj1, LV_ALIGN_CENTER, 0, 0);
lv_obj_align_to(obj_out_top, obj1, LV_ALIGN_OUT_TOP_MID, 0, 0);
lv_obj_align_to(obj_out_bottom, obj1, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
lv_obj_align_to(obj_out_left, obj1, LV_ALIGN_OUT_LEFT_MID, 0, 0);
lv_obj_align_to(obj_out_right, obj1, LV_ALIGN_OUT_RIGHT_MID, 0, 0);
lv_obj_align_to(obj2, obj_out_right, LV_ALIGN_OUT_TOP_MID, 0, 0);
#endif
当 if 后面为1时表示下面部分代码实现,效果如下:
可见轮廓并不参与盒子的大小设计,仅仅只是便于识别,当周围有其他盒子时,会被覆盖掉。
5、样式(styles)
概念
Styles 用于设置对象的外观。
- 样式是一个 lv_style_t 变量,它可以保存边框宽度、文本颜色等属性。
- 将样式(变量)分配给对象就可以改变其外观。在赋值过程中,可以指定目标部分和目标状态。
- 一个样式可以给多个对象使用(正常样式)。
- 样式可以级联,也就是可以将多个样式分配给一个对象。所以,我们不用将所有属性都在一个样式中指定,可以通过多个样式组合的形式指定。 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>)
参数 “obj” 就是要添加到的对象,“style” 是指向样式变量的指针,<selector> 是应添加样式的部分和状态的 OR-ed 值 (不能是互斥,否则就是清除标志,没法合并)。示例:
lv_obj_add_style(obj, &style_obj, 0); // 默认(常用)
lv_obj_add_style(obj, &style_obj, LV_STATE_PRESSED); // 在对象被按下时应用样式
获取样式属性
我们可以获取属性的最终值(考虑级联、继承、本地样式和转换),接口函数是这样的格式:
lv_obj_get_style_<property_name>(obj, <part>);
函数使用对象的当前状态,如果没有更好的候选对象,则返回默认值。 例如:
lv_color_t color = lv_obj_get_style_bg_color(obj, LV_PART_MAIN);
删除样式
删除对象的所有样式:
lv_obj_remove_style_all(obj);
删除对象的特定样式:
lv_obj_remove_style(obj, &style_obj, selector);
只有当 selector 与 lv_obj_add_style 中使用的 selector 匹配时,此函数才会删除 style。
如果 style 是空,那么会根据给出的 selector 检查并删除所有匹配的样式。
如果 selector 是 LV_STATE_ANY 或 LV_PART_ANY 就会删除具有任何状态或部分的样式。下面这个效果和lv_obj_remove_style_all 的效果是一样的:
lv_obj_remove_style(obj, NULL, LV_STATE_ANY | LV_PART_ANY );
查看样式属性
所有的可用的样式属性我们可以在文档或者代码中获取得到。
文档位置:
英文原版:https://docs.lvgl.io/8.1/overview/style-props.html
中文翻译:http://lvgl.100ask.net/8.1/overview/style-props.html
代码位置:
普通样式:lvgl/src/misc/lv_style_gen.h
本地样式:lvgl/src/core/lv_obj_style_gen.h
背景部分的属性
背景属性和我们前面学习的盒子模型关系很大,背景属性主要有一下这些:
- 背景(Background)
- 边界(Border)
- 轮廓(Outline)
- 阴影(Shadow)
- 填充(Padding)
- 宽度和高度
- 变换 X和Y变换
样式状态
对象可以处于以下状态的组合:
- 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_2 (0x4000) 自定义状态
- LV_STATE_USER_2 (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 可以从这里添加自定义部件。
代码示例(1)
void lv_100ask_demo_course_2_2_5(void)
{
lv_obj_t *obj = lv_obj_create(lv_scr_act());
lv_obj_align(obj,LV_ALIGN_CENTER,0,0); //将原始矩形设置在中心x=0,y=0处
static lv_style_t style_obj; //静态定义样式(注:这里只能静态定义或者函数外部全局定义,若在函数内部局部定义,会失效)
lv_style_init(&style_obj);
lv_style_set_bg_color(&style_obj,lv_color_hex(0x000000)); //设置背景色为黑色
lv_style_set_bg_color(&style_obj,lv_color_hex(0xc43e1c)); //设置背景色为红色(后设置的颜色会覆盖前面定义的颜色)
lv_style_set_bg_opa(&style_obj, LV_OPA_80); //设置背景透明度
lv_obj_add_style(obj,&style_obj,LV_STATE_PRESSED); //添加样式到对象,这里设置长按时样式生效
}
当按下矩形方框时,会显示红色,松开则复原。
代码示例(2)
显示一个滑杆,滑杆(Slider) 包含三个部分:背景、指标、旋钮。
示例体验:http://lvgl.100ask.net/8.1/widgets/core/slider.html#slider-with-custom-style
static void lv_example_slider_2(void)
{
/*Create a transition*/
static const lv_style_prop_t props[] = {LV_STYLE_BG_COLOR, 0};
static lv_style_transition_dsc_t transition_dsc;
lv_style_transition_dsc_init(&transition_dsc, props, lv_anim_path_linear, 300, 0, NULL);
static lv_style_t style_main; // 矩形背景部分
static lv_style_t style_indicator; // 指针部分
static lv_style_t style_knob; // 旋钮部分
static lv_style_t style_pressed_color; // 对象某部分被按下时,该部分样式应用的颜色
// 矩形背景部分
lv_style_init(&style_main);
lv_style_set_bg_opa(&style_main, LV_OPA_COVER);
lv_style_set_bg_color(&style_main, lv_color_hex3(0xbbb));
//lv_style_set_outline_width(&style_main, 5);
//lv_style_set_border_width(&style_main, 2);
lv_style_set_radius(&style_main, LV_RADIUS_CIRCLE);
lv_style_set_pad_ver(&style_main, -2); /*Makes the indicator larger*/
// 指针部分
lv_style_init(&style_indicator);
lv_style_set_bg_opa(&style_indicator, LV_OPA_COVER);
lv_style_set_bg_color(&style_indicator, lv_palette_main(LV_PALETTE_CYAN));
lv_style_set_radius(&style_indicator, LV_RADIUS_CIRCLE);
lv_style_set_transition(&style_indicator, &transition_dsc);
// 旋钮部分
lv_style_init(&style_knob);
lv_style_set_bg_opa(&style_knob, LV_OPA_COVER);
lv_style_set_bg_color(&style_knob, lv_palette_main(LV_PALETTE_CYAN));
lv_style_set_border_color(&style_knob, lv_palette_darken(LV_PALETTE_CYAN, 3));
lv_style_set_border_width(&style_knob, 2);
lv_style_set_radius(&style_knob, LV_RADIUS_CIRCLE);
lv_style_set_pad_all(&style_knob, 6); /*Makes the knob larger*/
lv_style_set_transition(&style_knob, &transition_dsc);
// 对象某部分被按下时,该部分样式应用的颜色
lv_style_init(&style_pressed_color);
lv_style_set_bg_color(&style_pressed_color, lv_palette_darken(LV_PALETTE_CYAN, 2));
/*Create a slider and add the style*/
lv_obj_t * slider = lv_slider_create(lv_scr_act());
// 体验流程:先体验 lv_obj_add_style 都被注释时的效果;然后一个个去掉注释
lv_obj_remove_style_all(slider); // 删除对象的所有样式,然后下面再添加我们自定义的样式
lv_obj_add_style(slider, &style_main, LV_PART_MAIN); // 矩形背景部分
lv_obj_add_style(slider, &style_indicator, LV_PART_INDICATOR); // 指针部分
lv_obj_add_style(slider, &style_pressed_color, LV_PART_INDICATOR | LV_STATE_PRESSED); // 当指针部分被按下的时候,指针部分应用该样式
lv_obj_add_style(slider, &style_knob, LV_PART_KNOB); // 旋钮部分,像按钮一样可以抓取调整值
lv_obj_add_style(slider, &style_pressed_color, LV_PART_KNOB | LV_STATE_PRESSED); // 当旋钮部分被按下的时候,旋钮部分应用该样式
lv_obj_center(slider);
}
void lv_100ask_demo_course_2_2_5(void)
{
// 滑杆示例
lv_example_slider_2();
}
当按下旋钮并滑动时,旋钮颜色以及矩形会发生颜色变化,长按时生效。
本地样式
除了“普通” 样式外,对象还可以存储 本地样式(私有样式) 。
本地样式与普通样式类似,但是它不能在其他对象之间共享。如果使用本地样式,将自动分配局部样式,并在删除对象时释放。本地样式对于向对象添加本地自定义很有用。
本地样式的接口函数是这样的格式:
lv_obj_set_style_local_<property_name>(obj, <value>, <selector>);
//三个参数分别是:对象、样式属性、样式状态
示例:
lv_obj_set_style_bg_color(obj, lv_color_hex(0xffffff), 0); // 设置背景色
lv_obj_set_style_bg_opa(obj, LV_OPA_50, 0); // 设置背景透明度
代码示例(3)
void lv_100ask_demo_course_2_2_5(void)
{
lv_obj_t *obj = lv_obj_create(lv_scr_act());
lv_obj_t *obj1 = lv_obj_create(lv_scr_act());
lv_obj_align(obj,LV_ALIGN_CENTER,0,0); //将原始矩形设置在中心x=0,y=0处
lv_obj_align(obj1,LV_ALIGN_BOTTOM_MID,0,0); //将原始矩形设置在中间底部x=0,y=0处
static lv_style_t style_obj; //静态定义样式(注:这里只能静态定义或者函数外部全局定义,若在函数内部局部定义,会失效)
lv_style_init(&style_obj);
lv_style_set_bg_color(&style_obj,lv_color_hex(0x000000)); //设置背景色为黑色(普通样式)
lv_style_set_bg_opa(&style_obj, LV_OPA_80); //设置背景透明度
lv_obj_set_style_bg_color(obj, lv_color_hex(0xc43e1c), LV_STATE_PRESSED); //设置背景颜色为红色(本地样式)
lv_obj_set_style_bg_color(obj1, lv_color_hex(0xc43e1c), LV_STATE_PRESSED); //设置背景颜色为红色(本地样式)
lv_obj_set_style_bg_opa(obj, LV_OPA_10, LV_STATE_PRESSED); //设置背景透明度(颜色浅)
lv_obj_set_style_bg_opa(obj1, LV_OPA_90, LV_STATE_PRESSED); //设置背景透明度(颜色深)
lv_obj_add_style(obj,&style_obj,LV_STATE_PRESSED); //添加(普通)样式到对象,这里设置长按时样式生效
lv_obj_add_style(obj1,&style_obj,LV_STATE_PRESSED); //添加(普通)样式到对象,这里设置长按时样式生效
#endif
}
序号1矩形按下颜色较浅,序号2矩形按下颜色较深,同时原先普通样式设置的黑色被本地样式设置的红色覆盖,体现出本地样式的优先级大于普通样式。
样式继承
某些属性(通常与文本相关)可以从父对象的样式继承。
只有没有在为对象设置样式属性的时候,才应用继承。 在这种情况下,如果这个属性是可继承的,那这个属性的值会在父类中检索,直到一个对象为该属性指定了一个值。父类将使用自己的状态来确定该值。 因此,如果按下按钮,并且文本颜色来自此处,则将使用按下的文本颜色。
void lv_100ask_demo_course_2_2_5(void)
{
static lv_style_t style_obj; //静态定义样式(注:这里只能静态定义或者函数外部全局定义,若在函数内部局部定义,会失效)
lv_style_init(&style_obj);
lv_obj_t *obj = lv_obj_create(lv_scr_act());
lv_obj_t *obj1 = lv_obj_create(lv_scr_act());
lv_obj_align(obj,LV_ALIGN_CENTER,0,0); //将原始矩形设置在中心x=0,y=0处
lv_obj_align(obj1,LV_ALIGN_BOTTOM_MID,0,0); //将原始矩形设置在中间底部x=0,y=0处
lv_style_set_text_color(&style_obj,lv_color_hex(0xc43e1c)); //设定父类文本颜色为红色
lv_obj_t *label = lv_label_create(obj); //创建新对象,设置父类为obj
lv_style_set_bg_color(&style_obj,lv_color_hex(0x000000)); //设置背景色为黑色
//lv_style_set_bg_color(&style_obj,lv_color_hex(0xc43e1c)); //设置背景色为红色(后设置的颜色会覆盖前面定义的颜色)
lv_style_set_bg_opa(&style_obj, LV_OPA_80); //设置背景透明度
//lv_obj_set_style_bg_color(obj, lv_color_hex(0xc43e1c), LV_STATE_PRESSED);
//lv_obj_set_style_bg_color(obj1, lv_color_hex(0xc43e1c), LV_STATE_PRESSED);
lv_obj_set_style_bg_opa(obj, LV_OPA_10, LV_STATE_PRESSED); //设置背景透明度(颜色浅)
lv_obj_set_style_bg_opa(obj1, LV_OPA_90, LV_STATE_PRESSED); //设置背景透明度(颜色深)
lv_obj_add_style(obj,&style_obj,LV_STATE_PRESSED); //添加样式到对象,这里设置长按时样式生效
lv_obj_add_style(obj1,&style_obj,LV_STATE_PRESSED); //添加样式到对象,这里设置长按时样式生效
}
按下中心位置的矩形时,文本呈现红色,可见文本颜色从设定的父类颜色得以继承。
过度特效
默认情况下,当一个对象改变状态(例如它被按下)时,新状态的新属性会立即设置。但是,通过转换,可以在状态更改时播放动画。 例如,按下按钮时,其背景颜色可以在 300 毫秒内动画显示为按下的颜色。
demo体验:
https://docs.lvgl.io/8.1/overview/style.html#transition http://lvgl.100ask.net/8.1/overview/style.html#transition
样式主题
主题是风格的集合。如果存在活动主题,LVGL将其应用于每个创建的部件(对象)。 这将为UI提供一个默认外观,然后可以通过添加更多样式对其进行修改。
demo体验:
https://docs.lvgl.io/8.1/overview/style.html#extending-the-current-theme http://lvgl.100ask.net/8.1/overview/style.html#extending-the-current-theme
6、事件(events)
什么是事件?
当发生用户可能感兴趣的事情时,LVGL 中会触发事件,例如当一个对象:
- 被点击
- 滚动
- 数值改变
- 重绘
- 等等
相关函数
添加事件:lv_obj_add_event_cb(obj, event_cb, event_code, user_data);
发送事件:lv_event_send(obj, event_cb, event_code, user_data);
删除事件:lv_obj_remove_event_cb(obj, event_cb);
lv_obj_remove_event_dsc(obj, event_dsc); //event_dsc 是lv_obj_add_event_cb 返回的指针
事件类型(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
代码示例(1)
现象:点击矩形变为黑色,同时进行打印"Hello LVGL"。
static void my_event_cb(lv_event_t * e) //回调函数
{
lv_obj_t * obj = lv_event_get_target(e); //获取触发事件的部件(对象)
lv_obj_set_style_bg_color(obj,lv_color_hex(0x000000),0); //点击设置背景色为黑色
printf("Hello LVGL\n"); //点击打印
}
void lv_100ask_demo_course_2_2_6(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act()); //创建基础对象
lv_obj_add_event_cb(obj,my_event_cb,LV_EVENT_CLICKED,NULL); //为obj添加事件回调函数,发出相应动作(第三个参数)时会执行事件(第二个参数),点击时能触发该回调函数
}
代码示例(2)
现象:按下矩形打印"LV_EVENT_PRESSED",同时矩形变为红色;长按矩形打印"LV_EVENT_LONG_PRESSED",同时矩形变为绿色。
static void my_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_PRESSED: //按下
lv_obj_set_style_bg_color(obj,lv_color_hex(0xc43e1c),0);//按下设置背景色为红色
printf("LV_EVENT_PRESSED\n");
break;
case LV_EVENT_LONG_PRESSED: //长按
lv_obj_set_style_bg_color(obj,lv_color_hex(0x4cbe37),0);//长按设置背景色为绿色
printf("LV_EVENT_LONG_PRESSED\n");
break;
}
}
void lv_100ask_demo_course_2_2_6(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act()); //创建基础对象
lv_obj_add_event_cb(obj,my_event_cb,LV_EVENT_ALL,NULL); //为obj添加事件回调函数,发出相应动作(第三个参数)时会执行事件(第二个参数),所有事件能触发该回调函数
}
代码示例(3)
现象:按下矩形打印"LV_EVENT_PRESSED",同时矩形变为红色;长按矩形打印"LV_EVENT_LONG_PRESSED",同时矩形变为绿色。
static void my_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); // 获取当前部件(对象)触发的事件代码
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(obj,lv_color_hex(0xc43e1c),0);//按下设置背景色为红色
printf("LV_EVENT_PRESSED\n");
break;
case LV_EVENT_LONG_PRESSED: //长按
lv_label_set_text(label,"LV_EVENT_LONG_PRESSED");
lv_obj_set_style_bg_color(obj,lv_color_hex(0x4cbe37),0);//长按设置背景色为绿色
printf("LV_EVENT_LONG_PRESSED\n");
break;
}
}
void lv_100ask_demo_course_2_2_6(void)
{
lv_obj_t * obj = lv_obj_create(lv_scr_act()); //创建基础对象
lv_obj_t * label = lv_label_create(lv_scr_act()); //创建label部件
lv_label_set_text(label,"text"); //设置label展示的文字
lv_obj_center(label); // 将对象与其父对象的中心对齐,这里的父对象是屏幕:lv_scr_act()
lv_obj_add_event_cb(obj,my_event_cb,LV_EVENT_ALL,label); //为obj添加事件回调函数,发出相应动作(第三个参数)时会执行事件(第二个参数),所有事件能触发该回调函数
}
事件回调函数的 lv_event_t 参数
事件回调函数只有一个参数,这个参数对我们的作为非常大,现在的版本提供这些功能:
static void my_event_cb(lv_event_t * event);
- 获取触发的事件代码:lv_event_code_t code = lv_event_get_code(e);
- 获取触发事件的对象:lv_obj_t * target = 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(e); 传递的用户数据 lv_event_get_param(e); 获取使用 lv_event_send(e);传递的用户数据
一个事件回调函数可给多个对象使用:我们创建了一个事件处理函数之后是可以给不同的对象使用的。
一个对象可以使用多个事件回调函数:我们创建的对象可以绑定多个事件,比如一个事件是处理点击类型的事件,一个事件处理按下类型的事件等等。
其他:如果传入的用户数据不一样,一个对象可以绑定同一个事件回调函数多次,事件将按照添加的顺序调用。例如:
lv_obj_add_event_cb(obj, my_clicked_event_cb, LV_EVENT_CLICKED, &num1);
lv_obj_add_event_cb(obj, my_clicked_event_cb, LV_EVENT_CLICKED, &num2);
事件冒泡
如果对象启用了 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); 获取事件冒泡的父对象。
代码示例(4)
现象:从大到小有四个矩形,大的为父对象,以此类推,所有子对象以最大的为父对象。当除了最大矩形被按下时,按下其他小的矩形时对应矩形和最大矩形会变红色,松手时恢复原样(松手时算点击 点击时删除所有背景颜色),如果按下最大的矩形,则其自己变为红色。
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;
}
}
void lv_100ask_demo_course_2_2_6(void)
{
lv_obj_t * obj1 = lv_obj_create(lv_scr_act()); //创建基础对象 obj1
lv_obj_set_size(obj1,450,350);
lv_obj_center(obj1); //将对象与其父对象的中心对齐,这里的父对象是屏幕:lv_scr_act()
lv_obj_t * obj2 = lv_obj_create(obj1); //以 obj1 创建一个基础对象 obj2
lv_obj_set_size(obj2,400,300);
lv_obj_center(obj2); //将对象与其父对象的中心对齐,这里的父对象是屏幕:obj1
lv_obj_add_flag(obj2, LV_OBJ_FLAG_EVENT_BUBBLE); //启用事件冒泡,将接收到的所有事件传播给父级
lv_obj_t * obj3 = lv_obj_create(obj1); //以 obj1 创建一个基础对象 obj3
lv_obj_set_size(obj3,350,250);
lv_obj_center(obj3); //将对象与其父对象的中心对齐,这里的父对象是屏幕:obj2
lv_obj_add_flag(obj3, LV_OBJ_FLAG_EVENT_BUBBLE); //启用事件冒泡,将接收到的所有事件传播给父级
lv_obj_t * obj4 = lv_obj_create(obj1); //以 obj1 创建一个基础对象 obj4
lv_obj_set_size(obj4,300,200);
lv_obj_center(obj4); //将对象与其父对象的中心对齐,这里的父对象是屏幕:obj3
lv_obj_add_flag(obj4, LV_OBJ_FLAG_EVENT_BUBBLE); //启用事件冒泡,将接收到的所有事件传播给父级
lv_obj_t * label = lv_label_create(lv_scr_act()); //创建label部件
lv_label_set_text(label,"text"); //设置label展示的文字
lv_obj_align_to(label,obj1,LV_ALIGN_OUT_TOP_MID,0,0); //将label相对于obj1对齐
lv_obj_add_event_cb(obj1,my_event_cb,LV_EVENT_ALL,label); //为obj1添加事件回调函数,发出相应动作(第三个参数)时会执行事件(第二个参数),所有事件能触发该回调函数
}