位置、尺寸和布局

概述

与 LVGL 的许多其他部分类似,设置坐标的概念受到了 CSS 的启发。LVGL 并未完全实现 CSS,而是实现了一个可比较的子集(有时会进行一些调整)。

简而言之,这意味着:

  • 显式设置的坐标存储在样式中(尺寸、位置、布局等)。

  • 支持最小宽度、最大宽度、最小高度、最大高度。

  • 支持像素、百分比和“内容”单位。

  • x=0;y=0 坐标表示父对象的左上角加上左/上内边距和边框宽度。

  • 宽度/高度表示完整尺寸,“内容区域”因内边距和边框宽度而较小。

  • 支持 Flexbox 和 Grid 布局的一个子集。

单位

  • 像素:简单地以像素为单位表示位置。整数始终表示像素。例如:lv_obj_set_x(btn, 10)

  • 百分比:对象或其父对象尺寸的百分比(取决于属性)。lv_pct(value) 将值转换为百分比。例如:lv_obj_set_width(btn, lv_pct(50))

  • LV_SIZE_CONTENT:一种特殊值,用于将对象的宽度/高度设置为包含所有子对象。类似于 CSS 中的 auto。例如:lv_obj_set_width(btn, LV_SIZE_CONTENT)

盒模型

LVGL 遵循 CSS 的 border-box 模型。 对象的“盒子”由以下部分组成:

  • 边界框:元素的宽度/高度。

  • 边框宽度:边框的宽度。

  • 内边距:对象边缘与其子对象之间的空间。

  • 内容:内容区域是边界框减去边框宽度和内边距后的大小。

LVGL 的盒模型:内容区域因内边距和边框宽度而小于边界框

边框绘制在边界框内。在边框内,LVGL 在放置对象的子对象时保留了“内边距”。

轮廓绘制在边界框外。

重要说明

本节描述了 LVGL 行为可能出乎意料的特殊情况。

延迟坐标计算

LVGL 不会立即重新计算所有坐标更改。这是为了提高性能。 相反,对象被标记为“脏”,在重新绘制屏幕之前,LVGL 会检查是否有“脏”对象。如果有,它会刷新它们的位置、大小和布局。

换句话说,如果您需要获取对象的坐标,而坐标刚刚发生了变化,则需要强制 LVGL 重新计算坐标。 为此,请调用 lv_obj_update_layout(obj)

大小和位置可能取决于父对象或布局。因此,lv_obj_update_layout 会重新计算 obj 所在屏幕上所有对象的坐标。

移除样式

使用样式部分所述,坐标也可以通过样式属性设置。 更准确地说,底层每个样式坐标相关属性都存储为样式属性。如果您使用 lv_obj_set_x(obj, 20),LVGL 会将 x=20 保存到对象的本地样式中。

这是一个内部机制,使用 LVGL 时并不重要。然而,有一种情况需要注意实现。如果通过以下方式移除对象的样式:

lv_obj_remove_style_all(obj)

lv_obj_remove_style(obj, NULL, LV_PART_MAIN);

先前设置的坐标也将被移除。

例如:

/*最终 obj1 的大小将恢复为默认值*/
lv_obj_set_size(obj1, 200, 100);  /*现在 obj1 的大小为 200;100*/
lv_obj_remove_style_all(obj1);    /*移除设置的大小*/

/*最终 obj2 的大小为 200;100 */
lv_obj_remove_style_all(obj2);
lv_obj_set_size(obj2, 200, 100);

位置

简单方法

要简单地设置对象的 x 和 y 坐标,请使用:

lv_obj_set_x(obj, 10);        //分别设置...
lv_obj_set_y(obj, 20);
lv_obj_set_pos(obj, 10, 20); 	//或在一个函数中设置

默认情况下,x 和 y 坐标是从父对象内容区域的左上角测量的。 例如,如果父对象在每一侧都有五个像素的内边距,则上述代码会将 obj 放置在 (15, 25),因为内容区域从内边距之后开始。

百分比值是根据父对象内容区域的大小计算的。

lv_obj_set_x(btn, lv_pct(10)); //x = 父对象内容区域宽度的 10%

对齐

在某些情况下,将定位的原点从默认的左上角更改为其他位置会很方便。如果将原点更改为例如右下角,则 (0,0) 位置表示:对齐到右下角。 要更改原点,请使用:

lv_obj_set_align(obj, align);

要更改对齐方式并设置新坐标:

lv_obj_align(obj, align, x, y);

可以使用以下对齐选项:

  • 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_obj_center(obj);

//具有相同效果
lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);

如果父对象的大小发生变化,子对象的设置对齐方式和位置会自动更新。

上述函数将对象对齐到其父对象。然而,也可以将对象对齐到任意参考对象。

lv_obj_align_to(obj_to_align, reference_obj, align, x, y);

除了上述对齐选项外,还可以使用以下选项将对象对齐到参考对象之外:

  • 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

例如,将标签对齐到按钮上方并水平居中:

lv_obj_align_to(label, btn, LV_ALIGN_OUT_TOP_MID, 0, -10);

请注意,与 lv_obj_align() 不同,lv_obj_align_to() 无法在对象的坐标或参考对象的坐标发生变化时重新对齐对象。

尺寸

简单方法

对象的宽度和高度也可以轻松设置:

lv_obj_set_width(obj, 200);       //分别设置...
lv_obj_set_height(obj, 100);
lv_obj_set_size(obj, 200, 100); 	//或在一个函数中设置

百分比值是根据父对象内容区域的大小计算的。例如,将对象的高度设置为屏幕高度:

lv_obj_set_height(obj, lv_pct(100));

尺寸设置支持一个特殊值:LV_SIZE_CONTENT。这意味着对象在相应方向上的大小将设置为其子对象的大小。 请注意,仅考虑右侧和底部的子对象,顶部和左侧的子对象将被裁剪。此限制使行为更具可预测性。

具有 LV_OBJ_FLAG_HIDDENLV_OBJ_FLAG_FLOATING 的对象将在 LV_SIZE_CONTENT 计算中被忽略。

上述函数设置对象边界框的大小,但也可以设置内容区域的大小。这意味着对象的边界框将因内边距的增加而变大。

lv_obj_set_content_width(obj, 50); //实际宽度:左内边距 + 50 + 右内边距
lv_obj_set_content_height(obj, 30); //实际宽度:顶部内边距 + 30 + 底部内边距

可以使用以下函数获取边界框和内容区域的大小:

lv_coord_t w = lv_obj_get_width(obj);
lv_coord_t h = lv_obj_get_height(obj);
lv_coord_t content_w = lv_obj_get_content_width(obj);
lv_coord_t content_h = lv_obj_get_content_height(obj);

使用样式

在底层,位置、尺寸和对齐属性是样式属性。 上述描述的“简单函数”隐藏了样式相关代码,以简化操作,并在对象的本地样式中设置位置、尺寸和对齐属性。

然而,使用样式设置坐标具有一些很大的优势:

  • 可以轻松地为多个对象一起设置宽度/高度等。例如,使所有滑块的大小为 100x10 像素。

  • 还可以在一个地方修改值。

  • 值可以被其他样式部分覆盖。例如,style_btn 默认使对象为 100x50,但添加 style_full_width 仅覆盖对象的宽度。

  • 对象可以根据状态具有不同的位置或大小。例如,在 LV_STATE_DEFAULT 中宽度为 100 像素,但在 LV_STATE_PRESSED 中为 120 像素。

  • 样式过渡可以用于使坐标更改平滑。

以下是使用样式设置对象大小的一些示例:

static lv_style_t style;
lv_style_init(&style);
lv_style_set_width(&style, 100);

lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_add_style(btn, &style, LV_PART_MAIN);

正如您将在下面看到的,尺寸和位置设置还有一些其他很棒的功能。 然而,为了保持 LVGL API 的简洁,仅最常见的坐标设置功能具有“简单”版本,而更复杂的功能可以通过样式使用。

平移

假设有 3 个按钮彼此相邻。它们的位置是如上所述设置的。 现在,您希望在按钮被按下时稍微向上移动。

实现此目的的一种方法是为按下状态设置一个新的 Y 坐标:

static lv_style_t style_normal;
lv_style_init(&style_normal);
lv_style_set_y(&style_normal, 100);

static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_y(&style_pressed, 80);

lv_obj_add_style(btn1, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn1, &style_pressed, LV_STATE_PRESSED);

lv_obj_add_style(btn2, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn2, &style_pressed, LV_STATE_PRESSED);

lv_obj_add_style(btn3, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn3, &style_pressed, LV_STATE_PRESSED);

这可以工作,但灵活性不高,因为按下的坐标是硬编码的。如果按钮不在 y=100,style_pressed 将无法按预期工作。可以使用平移来解决此问题:

static lv_style_t style_normal;
lv_style_init(&style_normal);
lv_style_set_y(&style_normal, 100);

static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_translate_y(&style_pressed, -20);

lv_obj_add_style(btn1, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn1, &style_pressed, LV_STATE_PRESSED);

lv_obj_add_style(btn2, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn2, &style_pressed, LV_STATE_PRESSED);

lv_obj_add_style(btn3, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn3, &style_pressed, LV_STATE_PRESSED);

平移是从对象的当前位置应用的。

百分比值也可以用于平移。百分比是相对于对象的大小(而不是父对象的大小)。例如,lv_pct(50) 将使对象移动其宽度/高度的一半。

平移是在布局计算之后应用的。因此,即使是布局对象的位置也可以被平移。

平移实际上会移动对象。这意味着它会使滚动条和 LV_SIZE_CONTENT 大小的对象对位置更改做出反应。

变换

与位置类似,对象的大小也可以相对于当前大小进行更改。 变换的宽度和高度会添加到对象的两侧。这意味着 10 像素的变换宽度会使对象宽度增加 2x10 像素。

与位置平移不同,尺寸变换不会使对象“真正”变大。换句话说,滚动条、布局和 LV_SIZE_CONTENT 不会对变换的大小做出反应。 因此,尺寸变换“仅仅”是一种视觉效果。

此代码在按钮被按下时放大按钮:

static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_transform_width(&style_pressed, 10);
lv_style_set_transform_height(&style_pressed, 10);

lv_obj_add_style(btn, &style_pressed, LV_STATE_PRESSED);

最小和最大尺寸

与 CSS 类似,LVGL 还支持 min-widthmax-widthmin-heightmax-height。这些是限制,防止对象的大小变得小于/大于这些值。 它们在尺寸由百分比或 LV_SIZE_CONTENT 设置时特别有用。

static lv_style_t style_max_height;
lv_style_init(&style_max_height);
lv_style_set_y(&style_max_height, 200);

lv_obj_set_height(obj, lv_pct(100));
lv_obj_add_style(obj, &style_max_height, LV_STATE_DEFAULT); //将高度限制为 200 像素

百分比值也可以使用,它们是相对于父对象内容区域的大小。

static lv_style_t style_max_height;
lv_style_init(&style_max_height);
lv_style_set_y(&style_max_height, lv_pct(50));

lv_obj_set_height(obj, lv_pct(100));
lv_obj_add_style(obj, &style_max_height, LV_STATE_DEFAULT); //将高度限制为父对象高度的一半

布局

概述

布局可以更新对象子对象的位置和大小。它们可用于自动将子对象排列成一行或一列,或以更复杂的形式排列。

布局设置的位置和大小会覆盖“正常”的 x、y、宽度和高度设置。

每种布局只有一个相同的函数:lv_obj_set_layout(obj, <LAYOUT_NAME>) 在对象上设置布局。 有关父对象和子对象的进一步设置,请参阅给定布局的文档。

内置布局

LVGL 提供了两个非常强大的布局:

  • Flexbox

  • Grid

两者都深受同名 CSS 布局的启发。

标志

有一些标志可用于对象,以影响它们在布局中的行为:

  • LV_OBJ_FLAG_HIDDEN 隐藏的对象在布局计算中被忽略。

  • LV_OBJ_FLAG_IGNORE_LAYOUT 布局会简单地忽略该对象。其坐标可以照常设置。

  • LV_OBJ_FLAG_FLOATINGLV_OBJ_FLAG_IGNORE_LAYOUT 相同,但具有 LV_OBJ_FLAG_FLOATING 的对象将在 LV_SIZE_CONTENT 计算中被忽略。

可以使用 lv_obj_add/clear_flag(obj, FLAG); 添加/移除这些标志。

添加新布局

LVGL 可以通过自定义布局自由扩展,如下所示:

uint32_t MY_LAYOUT;

...

MY_LAYOUT = lv_layout_register(my_layout_update, &user_data);

...

void my_layout_update(lv_obj_t * obj, void * user_data)
{
	/*如果需要重新定位/调整“obj”的子对象的大小,将自动调用此函数*/	
}

可以添加自定义样式属性,这些属性可以在更新回调中检索和使用。例如:

uint32_t MY_PROP;
...

LV_STYLE_MY_PROP = lv_style_register_prop();

...
static inline void lv_style_set_my_prop(lv_style_t * style, uint32_t value)
{
    lv_style_value_t v = {
        .num = (int32_t)value
    };
    lv_style_set_prop(style, LV_STYLE_MY_PROP, v);
}

示例