LVGL库入门教程04-样式

LVGL样式

LVGL样式概述

创建样式

在 LVGL 中,样式都是以对象的方式存在,一个对象可以描述一种样式。每个控件都可以独立添加样式,创建的样式之间互不影响。

可以使用 lv_style_t 类型创建一个样式并初始化:

static lv_style_t style;
lv_style_init(&style);

样式是延迟渲染的,因此需要使用 static 存储类别说明符或将其声明为全局变量。

样式是多方面的,不仅包括颜色和形状,还包括边距、边框,甚至动画变换效果等细节。

LVGL 中的样式从 CSS 中吸取了很多灵感,因此对样式的操作都类似 CSS

接下来,可以对得到的样式对象设置一些样式规则:

/* ... create and init style ... */
lv_style_set_radius(&style_btn_safe, 15);
lv_style_set_bg_opa(&style_btn_safe, LV_OPA_COVER);
lv_style_set_bg_color(&style_btn_safe, lv_palette_main(LV_PALETTE_GREEN));
lv_style_set_border_width(&style_btn_safe, 5);

所有的设置样式函数都是 lv_style_set_...() 形式,完整的样式规则将在之后介绍。未指定的样式规则将保持控件的默认样式。

然后就可以将样式分配给控件,例如,以下创建了一个按钮并利用 lv_obj_add_style() 函数设置其样式为刚才创建的样式了:

lv_obj_t* btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, "Button");
lv_obj_add_style(btn, &style_btn_safe, 0);

这样按钮的外观就会被改变了,效果为:

以上修改了按钮的颜色,如果对颜色的创建过程不太理解也不要紧,以后会介绍颜色的代码描述。可以简单地将 GREEN 改成其它颜色名来改变不同的颜色。设置样式的函数最后有一个参数 0 ,它代表的是样式的选择器,将会在接下来介绍。

一个文件内可以创建多种不同的样式对象,这样同一个界面中按钮可以表现出多种不同的样式。

样式的级联

所谓“级联”(cascading),指的是将多个样式分配给一个对象。此时如果多个样式间设置的样式属性有重复,那么将使用最后设置的样式值。也就是说,后设置的样式具有更高的优先级。

控件在创建时可以视为同时添加了一个默认的样式,因此在代码中指定的任意样式都会覆盖默认的样式。

还有一种特殊的局部样式(local styles),局部样式具有最高的优先级,但只对单个控件有效。局部样式的创建类似如下:

lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_RED), 0);

它们都是 lv_obj_set_style_...() 形式的函数。

局部样式一旦被设置,只能再次通过局部样式修改回来。因此,局部样式需要谨慎使用。

选择器

LVGL 的选择器(selector)与 CSS 不同。在 CSS 中,样式通过选择器选择需要作用的元素;而 LVGL 中,样式通过选择器作用于控件的部分。

要明白什么是控件的部分,需要分析控件的组成。例如,以下代码可以创建一个滑块(slider)控件:

lv_obj_t* slider01 = lv_slider_create(lv_scr_act());

滑块是一种调整类型的控件,用户可以通过拖动它的把手(knob)来调节滑块当前的数值。滑块默认的表现形式为:

仔细观察滑块的组成,滑块可以由主体外形、把手(knob)和进度指示条(indicator)组成。可以通过选择器单独设置这三个构成部分的样式。例如,假设需要更改这三个部分的样式,就可以通过选择器分别指定修改的结构:

static lv_style_t style_slider_main;
lv_style_init(&style_slider_main);
lv_style_set_bg_opa(&style_slider_main, LV_OPA_COVER);
lv_style_set_bg_color(&style_slider_main, lv_palette_main(LV_PALETTE_YELLOW));
/* using selectors */
lv_obj_add_style(slider01, &style_slider_main, LV_PART_MAIN);
lv_obj_set_style_radius(slider01, 0, LV_PART_KNOB);
lv_obj_set_style_bg_color(slider01, lv_palette_main(LV_PALETTE_RED), LV_PART_INDICATOR);

这里分别使用全局样式和局部样式修改控件的各个部分。修改之后,把手部分变成了方形,主体和进度进度的颜色都发生了变化:

选择器的一个更妙的用途是和控件状态做按位或运算,从而可以修改某个部分在某个状态下的样式。例如,选择器

lv_obj_add_style(slider01, &style_slider_main, 
                 LV_PART_MAIN | LV_STATE_PRESSED);

使滑块的主体只有在按下时才会使用该样式(颜色被改变):

image

LVGL 的选择器在表现形式上效果非常像 CSS 的伪元素和伪类选择器。

滑块在拖动过程中,会不断触发 LV_EVENT_VALUE_CHANGED 事件,可以使用函数

static inline int32_t lv_slider_get_value(const lv_obj_t* obj);

获取当前获取的滑块数值(介于 0~100 )。更多的滑块 API 可以参考官方文档的介绍。

接下来详细地介绍样式可以设置的一些属性。

样式属性

尺寸和位置

要理解尺寸和位置是如何起作用的,首先要理解 LVGL 的盒子模型。官方文档给出了一张图,可以很好地描述一个控件的框架结构:

在设置尺寸的时候,长和宽指的是包括边框(border)厚度的长宽,也就是不包括轮廓(outline)的总长宽。

在设置位置的时候,设置的坐标指的是 border 左上角相对父容器的 Content area 的坐标,也就是说如果设置坐标为 0 的话,轮廓(outline)可能会被父容器的边框(border)遮盖。

下表总结了尺寸与位置有关的可用属性有:

属性 描述 默认值
width 宽度 由控件类别决定
min_width 最小宽度 0
max_width 最大宽度 屏幕的宽度
height 高度 由控件类别决定
min_height 最小宽度 0
max_height 最大宽度 屏幕的高度
align 对齐方式 左上方
x 对齐后在水平方向的偏移量 0
y 对齐后在竖直方向的偏移量 0

注意这里有一个最小或最大的宽度和高度,在上一节介绍 flex 和 grid 布局时就展示过控件宽度随布局自动调整的情况,因此可以给它们提供一个阈值防止过大或过小。

不过上一节还有一个地方没有提到:在设置宽度和高度时,除了使用确定的数值外,还可以使用百分比值 lv_pct(x) 来设置控件相对父容器的 Content area 的大小或位置。例如,样式

lv_style_set_width(&style, lv_pct(25));
lv_style_set_x(&style, lv_pct(50));

可以让一个控件的水平尺寸占据父容器的 1/2~3/4 的位置:

对于父容器而言,还可以使用 LV_SIZE_CONTENT 特殊单位调整其尺寸至可以容纳所有包含控件的合适值。例如,按钮就是一个这样的容器,它的默认样式就通过该值使得其宽度和高度可以自动适应包含的标签尺寸。

边框和边距

上图展示的文本框就有一个深灰色的边框。边框就无需额外描述了,与边框有关的样式属性有:

属性 描述 默认值
border_width 边框宽度,只能用绝对宽度描述 0
border_side 绘制哪些部分的边框 LV_SIDE_ALL
border_post 绘制顺序,设置 true 表示包含的子控件绘制完成了再绘制边框 false
... 与颜色有关的属性将在之后介绍

边框和主体部分之间被边距(padding)隔开。和边距有关的样式属性有:

属性 描述 默认值
pad_top 上边距 0
pad_bottom 下边距 0
pad_left 左边距 0
pad_right 右边距 0
pad_row 当控件拥有布局时,每行间的间距 0
pad_column 当控件拥有布局时,每列间的间距 0

不过在设置布局时,还提供了几个简写属性:可以使用 ...pad_all() 一并设置上下左右的边距;或使用 ...pad_hor()...pad_ver() 设置水平和垂直的边距;还可以使用 ...pad_gap() 设置行和列的间距。

轮廓

轮廓(outline)类似边框,但轮廓并不算在一个控件的主体内,因此设置坐标、尺寸等属性时都不包含轮廓的尺寸。

轮廓可设置的属性远比边框少。下表列出了轮廓的一些属性:

属性 描述 默认值
outline_width 轮廓宽度 0
outline_pad 轮廓到主体的间距 0
... 与颜色有关的属性将在之后介绍

轮廓和边框最根本的差异是两者不是同一个东西,因此可以在同一个元素同时使用不同样式的轮廓的边框来实现一些有趣的效果,例如:

lv_style_set_radius(&style, 0);
lv_style_set_border_color(&style, lv_palette_main(LV_PALETTE_GREY));
lv_style_set_border_width(&style, 5);
lv_style_set_border_opa(&style, LV_OPA_COVER);
lv_style_set_border_side(&style, LV_BORDER_SIDE_BOTTOM | LV_BORDER_SIDE_RIGHT);
lv_style_set_outline_width(&style, 4);
lv_style_set_outline_pad(&style, 1);
lv_style_set_outline_color(&style, lv_palette_lighten(LV_PALETTE_GREY, 1));

表现效果为:

阴影

阴影可以使控件看起来有立体感。下表列出了设置阴影的一些属性:

属性 描述 默认值
shadow_width 设置阴影的模糊半径 0
shadow_ofs_x 设置阴影的水平偏移量 0
shadow_ofs_y 设置阴影的垂直偏移量 0
shadow_spread 设置阴影的放大量 0
... 与颜色有关的属性将在之后介绍

例如,以下设置模糊半径为 50 的蓝色阴影:

lv_style_set_shadow_width(&style, 50);
lv_style_set_shadow_color(&style, lv_palette_main(LV_PALETTE_BLUE));

效果为:

以下设置放大有偏移的红色阴影:

lv_style_shadow_color(&style, lv_palette_main(LV_PALETTE_RED))
lv_style_set_shadow_width(&style, 15)
lv_style_set_shadow_ofs_x(&style, 10)
lv_style_set_shadow_ofs_y(&style, 20)
lv_style_set_shadow_spread(&style, 10)

效果为:

LVGL 中无法给同一个控件设置多个阴影叠加,从而实现更复杂的效果,这是比较可惜的一点。

文本样式

在创建控件时经常要使用文字,下表列出了能影响文字效果的一些属性:

属性 描述 默认值
text_font 设置文字的字体 默认字体
text_letter_space 字符间隔 0
text_line_space 设置多行文本的行间距 0
text_decor 设置文本装饰(下划线或删除线) LV_TEXT_DECOR_NONE
text_align 设置文本对齐方式 LV_TEXT_ALIGN_AUTO
... 与颜色有关的属性将在之后介绍

需要注意的是,文本的样式是可继承的,意思是如果子控件没有特别指定的话,它会使用父容器设置的文本样式。

在一段文本内可能存在许多种样式,对此,可以使用类似 CSS 的 span 来拆分样式在文本内的作用域。为了创建 span ,首先需要创建一个 span-group :

lv_obj_t* spangroup = lv_spangroup_create(lv_scr_act());
lv_obj_set_size(spangroup, 160, LV_SIZE_CONTENT);

创建的 span-group 和一般的控件没什么区别,可以给它添加一些样式:

lv_obj_set_style_border_color(spangroup, lv_palette_main(LV_PALETTE_BLUE), 0);
lv_obj_set_style_border_width(spangroup, 1, 0);
lv_obj_set_style_pad_all(spangroup, 5, 0);

span-group 提供的以下函数使得它相比标签更适合用来处理大段的文本:

函数 介绍
lv_spangroup_set_align(obj, align) 设置文本的对齐
lv_spangroup_set_overflow(obj, overflow) 控制溢出文本的处理方式
lv_spangroup_set_indent(obj, indent) 设置文本的首行缩进,单位为像素
lv_spangroup_set_mode(obj, mode) 设置对多行文本的折行处理,可以参见枚举 lv_span_mode_t

有了 span-group 以后,可以使用以下代码从中创建一个 span 并设置文本:

lv_span_t* span = lv_spangroup_new_span(spangroup);
lv_span_set_text(span, "LVGL is an open-source graphics library");

每一个 span 都提供了一个独立的样式接口,可以单独设置范围内文本的样式:

lv_style_set_text_color(&span->style, lv_palette_main(LV_PALETTE_BLUE));

一个 span-group 可以创建多个 span ,并且它们的样式效果互不影响:

span = lv_spangroup_new_span(spangroup);
lv_span_set_text(span, "providing everything");
lv_style_set_text_decor(&span->style, LV_TEXT_DECOR_UNDERLINE);
lv_style_set_text_font(&span->style, &lv_font_montserrat_20);
/* ... */
span = lv_spangroup_new_span(spangroup);
lv_span_set_text(span, "to create embedded GUI");

效果为:

可以注意到默认的 span-group 是没什么样式的。span-group 还有很多的 API ,具体可以参照官方文档的相关介绍。

其它样式

下表列出了一些其它的样式属性:

属性 描述 默认值
radius 设置控件的圆角,该属性会一并影响边框和轮廓 0,即无圆角
clip_corner 如果有圆角,是否要将 Content-aera 超出圆角的部分去除
layout 设置控件的布局方式 0
base_dir 设置文字的书写方向,它会同时影响布局的方向 默认书写方向
... 与颜色有关的属性将在之后介绍

在设置半径时可以使用百分数,例如 lv_pct(50) 将使控件变成圆形。

以上列出了大部分的样式属性,但是除了颜色外还有许多样式没有介绍,例如变换、动画、渐变等,这些留到之后介绍。LVGL 中还存在一些特殊的样式,它们是为相应的控件设计的,接下来介绍这些控件及样式。

基本图形:直线和弧线

直线

LVGL 中的直线(line)实际上指的是折线,因为它可以一次性连续绘制多条相接的线段。为了绘制折线,首先要准备一些端点的坐标:

static lv_point_t line_points[] = { {217, 36}, {35, 49}, {281, 163}, {110, 162}, {257, 111} };

然后可以通过这些端点来创建折线:

lv_obj_t* line1 = lv_line_create(lv_scr_act());
lv_line_set_points(line1, line_points, 5);

效果为:

创建的折线作为一个整体,实际上也是一个控件,当然可以给它加上各种属性:

static lv_style_t style_line;
lv_style_init(&style_line);
lv_style_set_align(&style_line, LV_ALIGN_TOP_MID);
lv_style_set_border_width(&style_line, 4);
lv_obj_add_style(line01, &style_line, 0);

效果为:

折线拥有一些特殊的样式属性,是其它控件所没有的。下表列出了折线的特殊属性:

属性 描述 默认值
line_width 设置线段宽度 0
line_dash_width 设置虚线实部分的距离 0
line_dash_gap 设置虚线虚部分的距离 0
line_rounded 设置线段端点是否为圆角
line_color 设置线段颜色 黑色
line_opa 设置颜色透明度 不透明

注意,虚线只对水平和垂直的线段有效,并且只有两个属性都不为 0 才有虚线的效果。

例如,样式:

lv_style_set_line_color(&style_line, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_line_width(&style_line, 8);

表现效果为:

如果再添加上:

lv_style_set_line_dash_width(&style_line, 10)
lv_style_set_line_dash_gap(&style_line, 5)
lv_style_set_line_rounded(&style_line, true);

那么效果就变成:

关于折线还有一个函数 lv_line_set_y_invert(lv_obj_t *obj, bool en) 可以用来控制绘制的折线垂直翻转(即翻转 y 轴)。除此之外折线并没有什么可以介绍的。在后续还会介绍图表,可以绘制更美观的折线效果。

圆弧

LVGL 中的圆弧(arc)尽管和直线同属于基础控件,但圆弧的功能远比直线丰富,甚至 API 比起滑块这些复杂的控件都多。

首先简单创建一个圆弧,查看它的默认效果:

lv_obj_t* arc01 = lv_arc_create(lv_scr_act());

默认的效果为:

可以看出圆弧的在默认情况下,它的表现形式实际上就是弧形的滑块。如果想要得到纯粹的圆弧,可以将圆弧的把手删除:

lv_obj_remove_style(arc01, NULL, LV_PART_KNOB);
lv_obj_clear_flag(arc01, LV_OBJ_FLAG_CLICKABLE);

这里做了两件事:首先是将把手的样式删除,这里第二个参数 NULL 表示删去全部样式;其次将圆弧的可点击标志位清除,使它不再能接收用户的点击事件。这样圆弧看起来就纯粹多了:

还可以进一步删去圆弧的指示条(indicator),让它更像传统的圆弧。

默认的圆弧是开口向下的 270° 圆弧。为了设置圆弧的形状,可以使用函数

void lv_arc_set_angles(lv_obj_t *obj, uint16_t start, uint16_t end);
void lv_arc_set_bg_angles(lv_obj_t *obj, uint16_t start, uint16_t end);

分别修改前景和背景的圆弧起止范围,单位为角度。注意,圆弧的角度 0° 是正右方向,90° 是正下方向,以此类推。这两个函数都有单独设置起或止位置的版本。例如,设置

lv_arc_set_bg_angles(arc01, 0, 270);
lv_arc_set_end_angle(arc01, 180);

可以将圆弧的角度调整为:

圆弧也像直线一样具有特殊的样式,下表列出了圆弧具有的样式属性:

属性 描述 默认值
arc_width 设置圆弧宽度 0
arc_rounded 设置圆弧端点是否为圆角
arc_color 设置圆弧颜色 黑色
arc_opa 设置圆弧透明度 不透明
arc_img_src 设置圆弧填充图片 无填充图片

以上是官方文档的介绍,但这个默认值显然与实际不符。之所以会这样,原因是在 lv_conf.h 大约 514 行,启用过默认的样式:

/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1

而该样式在初始化时,就会修改包括圆弧在内的一些样式,因此圆弧、按钮等控件才默认表现为这个模样。

圆弧可以作为一个基准让控件对齐。例如,可以使用

lv_arc_rotate_obj_to_angle(arc01, label, 25);

让一个标签旋转对齐圆弧的把手,第三个参数为半径的偏移量,效果为:

与其说是对齐把手,更准确的说法是对齐圆弧当前的值。例如,可以通过以下函数改变圆弧的值:

lv_arc_set_value(arc01, 20);

这样效果就很明显了:

圆弧默认的取值范围是 0~100 ,也可以通过 lv_arc_set_range(obj, min, max) 函数修改这一取值范围。除此之外,还有另一个函数 lv_arc_align_obj_to_angle(obj, obj_to_align, r_offset) 只对齐控件而不发生旋转。另外需要注意,应该先对齐圆弧后,再设置标签的对齐,否则标签会因为不是包含关系而不随之更新位置。

总体来说,圆弧因为不是纯粹的圆弧,因此它具有滑块的各种特征,例如可以响应 LV_EVENT_VALUE_CHANGED 事件,可以使用 lv_arc_get_value(obj) 获取值等。

首发于:http://frozencandles.fun/archives/361

参考资料/延伸阅读

https://docs.lvgl.io/master/overview/style.html

官方文档——样式简介

https://docs.lvgl.io/master/overview/style-props.html

官方文档——所有的样式属性简介

posted @ 2022-06-18 11:15  冰封残烛  阅读(4232)  评论(0编辑  收藏  举报