35. 布局管理

一、布局管理

  我们可以使用 lv_obj_set_pos(obj, x, y) 函数调整一个控件的位置(或者使用类似的函数单独调整一个方向的坐标),将它放在相对父容器左上角的合适位置。如果两个控件间没有包含关系,可以使用 lv_obj_align_to(obj, base, align, x_ofs, y_ofs) 函数设置两个控件的相对对齐方式。不过这种布局方式非常死板,因为绝对坐标一旦设定就不能自动调整;而且当控件数量较多时,也很难确定合适的坐标值。

  这种对齐的方式对于控件不多的情况下来说是足够了,但是有些时候需要对很多并列的控件布局(例如,一个计算机界面的所有按钮)。这个时候常规的对齐方式就难以满足需求了。因此,LVGL 提供了两种更复杂的布局方式:弹性盒子(flex)和 网格布局(grid)。如果不添加任何布局方式,那么所有的控件都会堆放在左上角。

  如果,我们要启用 Flex 布局,需要在 lv_conf.h 文件中把 LV_USE_FLEX 配置项置 1 即可启用 Flex 布局。如果,我们要启动 Grid 布局,需要在 lv_conf.h 文件中把 LV_USE_GRID 配置项置 1 即可启用 Grid 布局。

开启布局

二、弹性布局

  Flexible Box 模型,通常被称为 flexbox,是一种一维的布局模型。它给 flexbox 的子元素之间提供了强大的空间分布和对齐能力。它可以将对象排列成行或列,并处理环绕,调整对象和行/列之间的间距,处理增长以使对象填充剩余空间的最小/最大宽度和高度。

  当使用 flex 布局时,首先想到的是两根轴线,它们分别为 主轴交叉轴。主轴是 flex 方向,另一根轴垂直于它。我们使用 flexbox 的所有属性都跟这两根轴线有关。

  主轴:是定义对象的放置方向的,在 LVGL 中,它一般可取八个值,如下所示:

typedef enum {
    LV_FLEX_FLOW_ROW                 = 0x00,                                                    // 将子类们排成一行并不换行
    LV_FLEX_FLOW_COLUMN              = _LV_FLEX_COLUMN,                                         // 将子类们排成一列并不换行
    LV_FLEX_FLOW_ROW_WRAP            = LV_FLEX_FLOW_ROW | _LV_FLEX_WRAP,                        // 将子类们排成一行并换行
    LV_FLEX_FLOW_ROW_REVERSE         = LV_FLEX_FLOW_ROW | _LV_FLEX_REVERSE,                     // 将子类们排成一行并不换行且顺序相反
    LV_FLEX_FLOW_ROW_WRAP_REVERSE    = LV_FLEX_FLOW_ROW | _LV_FLEX_WRAP | _LV_FLEX_REVERSE,     // 将子类们排成一行并换行且顺序相反
    LV_FLEX_FLOW_COLUMN_WRAP         = LV_FLEX_FLOW_COLUMN | _LV_FLEX_WRAP,                     // 将子类们排成一列并换行
    LV_FLEX_FLOW_COLUMN_REVERSE      = LV_FLEX_FLOW_COLUMN | _LV_FLEX_REVERSE,                  // 将子类们排成一列并不换行且顺序相反
    LV_FLEX_FLOW_COLUMN_WRAP_REVERSE = LV_FLEX_FLOW_COLUMN | _LV_FLEX_WRAP | _LV_FLEX_REVERSE,  // 将子类们排成一列并换行且顺序相反
} lv_flex_flow_t;

  由此可知,如果我们设置 LV_FLEX_FLOW_ROW ~ LV_FLEX_FLOW_ROW_WRAP_REVERSE 为主轴配置项时,容器的主轴将沿着 inline 方向延伸,如下图所示:

主轴行设置示意图

  从上图可知:容器就是绘画在显示屏上的部件,如果有些子类超出了容器范围,则系统自动使能滚动条。带有 WRAP 的配置项会把超出容器的子类以换行的形式添加在第二行中,带有 REVEESW 的配置项会把所有子类以翻转的顺序排列。

  如果我们设置 LV_FLEX_FLOW_COLUMN ~ LV_FLEX_FLOW_COLUMN_WRAP_REVERSE 为主轴配置项时,容器的主轴会沿着上下方向延伸,也就是 block 排列的方向,如下图所示:

主轴列设置示意图

  从上图可知:容器就是绘画在显示屏上的部件,如果有些子类超出了容器范围,则系统自动使能滚动条。带有 WRAP 的配置项会把超出容器的子类以换列的形式添加在第二列中,带有 REVEESW 的配置项会把所有子类以翻转的顺序排列。

  交叉轴:交叉轴垂直于主轴,所以如果你的 flex-direction(主轴)设成了 ROW 的话,交叉轴的方向就是沿着列向下的,如下图所示:

列的交叉主轴

  如果你的 flex-direction(主轴)设成了 COLUMN 的话,交叉轴就是水平方向,如下图所示:

行的交叉主轴

  在 LVGL 中,使用 Flex 布局有两种方式,第一种是使用 lv_obj_set_layout() 函数方法设置对象启用 Flex 布局,第二种是使用 lv_obj_set_flex_flow() 函数启用 Flex 布局。

// 第一种方式
static lv_style_t style;
lv_style_init(&style);
lv_style_set_flex_flow(&style, LV_FLEX_FLOW_ROW_WRAP);

/* 设置 Flex 布局 */
lv_style_set_layout(&style, LV_LAYOUT_FLEX);
// 第二种方式
lv_obj_t* cont_row = lv_obj_create(lv_scr_act());

// 设置 Flex 布局 
lv_obj_set_flex_flow(cont_row, LV_FLEX_FLOW_ROW_WRAP);

  Flexbox 的一个关键特性是能够设置 flex 元素沿主轴方向和交叉轴方向的对齐方式以及它们之间的空间分配,这时,我们使用函数为 lv_obj_set_flex_align() 函数。

/**
 * @brief 设置对齐方式
 * 
 * @param obj 指向设置对齐的对象
 * @param main_place 主轴上的对象对齐
 * @param cross_place 交叉轴上的对象对齐
 * @param track_place 行/列交叉轴上的对象对齐
 */
void lv_obj_set_flex_align(lv_obj_t * obj, lv_flex_align_t main_place, lv_flex_align_t cross_place, lv_flex_align_t track_place);

  main_place、cross_place 和 track_place 形参的转入值如下表所示:

typedef enum {
    LV_FLEX_ALIGN_START,                    // 水平方向左上和垂直上方向上(默认)
    LV_FLEX_ALIGN_END,                      // 水平方向右侧和垂直底部
    LV_FLEX_ALIGN_CENTER,                   // 居中
    LV_FLEX_ALIGN_SPACE_EVENLY,             // 任何两个对象之间的间距(它们的边缘空间相等),但不适用于track__place形参
    LV_FLEX_ALIGN_SPACE_AROUND,             // 对象在轨道上均匀分布,周围空间相等,但不适用于track_place形参
    LV_FLEX_ALIGN_SPACE_BETWEEN,            // 对象在轨道上均匀分布:第一个对象在开始行,最后一个项目在结束行,但不适用于track_cross_place形参
} lv_flex_align_t;

main_place形参传入的配置项示意图

  track_place 形参只能传入 LV_FLEX_ALIGN_STARTLV_FLEX_ALIGN_ENDLV_FLEX_ALIGN_CENTER 配置项,它们分别代表显示的位置。

track_place形参传入的配置项示意图

  如果 track_place 形参设置为 LV_FLEX_ALIGN_START,则该容器显示子类 1 和 2;如果 track_place 形参设置为 LV_FLEX_ALIGN_END,则该容器显示子类 5 和 6;如果 track_place 形参设置为 LV_FLEX_ALIGN_CENTER,则该容器显示子类 3 和 4。

  cross_place 形参一般在子类对象具有不同的高度时会起作用。

  flex-grow 若被赋值为一个正整数,它沿主轴方向增长尺寸。这会使该元素延展,并占据此方向轴上的可用空间(available space)。如果有其他元素也被允许延展,那么它们会各自占据可用空间的一部分。关于这个 grow 属性,LVGL 提供了 lv_obj_set_flex_grow() 函数来设置,该函数具有两个形参,第一个形参指向要增长的对象,而第二个形参表示增长的倍数。

/**
 * @brief 设置增长
 * 
 * @param obj 指向要增长的对象
 * @param grow 增长的倍数,如果为0时禁用grow属性
 */
void lv_obj_set_flex_grow(lv_obj_t * obj, uint8_t grow);

  Flex-gap 属性用来设置元素列之间的间隔(gutter)大小。如果读者不希望对象之间有任何填充,可调用 lv_style_set_pad_column() 函数设置。

void lv_style_set_pad_row(lv_style_t * style, int32_t value);
void lv_style_set_pad_column(lv_style_t * style, int32_t value);

三、网格布局

  Grid 网格是一组相交的水平线和垂直线,它定义了网格的列和行。我们可以将网格元素放置在与这些行和列相关的位置上。简单来说:Grid 网格可以将对象排列到具有行或列(轨道)的二维“表”中,该对象可以跨越多个列或行。轨道的大小可以设置为 像素LV_GRID_CONTENT空闲单元(FR)以按比例分配空闲空间。

  Grid 描述符是用来描述行/列的数量以及单元格的像素,它一般使用两个数组来描述网格的行数和列数。这些数组的最后一个元素必须是 LV_GRID_TEMPLATE_LAST 配置项。

  我们可以调用 lv_obj_set_style_grid_column_dsc_array() 函数和 lv_obj_set_style_grid_row_dsc_array() 函数创建网格的。

void lv_obj_set_style_grid_column_dsc_array(lv_obj_t * obj, const int32_t * value, lv_style_selector_t selector);
void lv_obj_set_style_grid_row_dsc_array(lv_obj_t * obj, const int32_t * value, lv_style_selector_t selector);

  当然除了以像素为单位的简单设置之外,我们还可以使用两个特殊值:

#define LV_GRID_CONTENT        (LV_COORD_MAX - 101)             // 将宽度设置为此轨道上最大的子类
#define LV_GRID_FR(x)          (LV_COORD_MAX - 100 + x)         // 告诉该轨道应使用剩余空间的哪一部分,数值越大,空间越大

  默认情况下,我们的子项不会自动添加到网格中。这些子类需要读者手动添加到单元格中,这个添加方式需要读者调用 lv_obj_set_grid_cell() 把子类对象添加到指定网格位置。

/**
 * @brief 设置对象的单元格
 * 
 * @param obj 指向对象的指针
 * @param x_align 单元格中的垂直对齐方
 * @param col_pos 列号
 * @param col_span 要取的列数
 * @param y_align 单元格中的水平对齐方式
 * @param row_pos 行号
 * @param row_span 获取的行数
 */
void lv_obj_set_grid_cell(lv_obj_t * obj, lv_grid_align_t x_align, int32_t col_pos, int32_t col_span,
                          lv_grid_align_t y_align, int32_t row_pos, int32_t row_span);

  Grid 对齐使用的是 lv_obj_set_grid_align() 函数

void lv_obj_set_grid_align(lv_obj_t * obj, lv_grid_align_t column_align, lv_grid_align_t row_align);

四、实验例程

4.1、弹性布局

#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"

int main(void)
{   
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    SPI_Simulate_Init();
    // SRAM_Init();
    TIM_Base_Init(&g_tim6_handle, TIM6, 83, 999);
    __HAL_TIM_CLEAR_IT(&g_tim6_handle, TIM_IT_UPDATE);                          // 清除更新中断标志位
    HAL_TIM_Base_Start_IT(&g_tim6_handle);                                      // 使能更新中断,并启动计数器

    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();

    // 测试代码
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_flex_flow(&style, LV_FLEX_FLOW_ROW);
    lv_style_set_layout(&style, LV_LAYOUT_FLEX);

    lv_obj_t* cont_row = lv_obj_create(lv_scr_act());
    lv_obj_set_size(cont_row, 300, 70);
    lv_obj_add_style(cont_row, &style, 0);


    lv_obj_t * cont_col = lv_obj_create(lv_scr_act());
    lv_obj_set_size(cont_col, 200, 150);
    lv_obj_align_to(cont_col, cont_row, LV_ALIGN_OUT_BOTTOM_MID, 0, 5);
    lv_obj_set_flex_flow(cont_col, LV_FLEX_FLOW_COLUMN);

    for(uint32_t i = 0; i < 10; i++) 
    {
        lv_obj_t * obj;
        lv_obj_t * label;

        obj = lv_button_create(cont_row);
        lv_obj_set_size(obj, 100, LV_PCT(100));

        label = lv_label_create(obj);
        lv_label_set_text_fmt(label, "Item: %"LV_PRIu32"", i);
        lv_obj_center(label);

        obj = lv_button_create(cont_col);
        lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);

        label = lv_label_create(obj);
        lv_label_set_text_fmt(label, "Item: %"LV_PRIu32, i);
        lv_obj_center(label);
    }

    while (1)
    {
        lv_timer_handler();
        Delay_ms(5);
    }
  
    return 0;
}

4.2、网格布局

#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"

int main(void)
{   
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    SPI_Simulate_Init();
    // SRAM_Init();
    TIM_Base_Init(&g_tim6_handle, TIM6, 83, 999);
    __HAL_TIM_CLEAR_IT(&g_tim6_handle, TIM_IT_UPDATE);                          // 清除更新中断标志位
    HAL_TIM_Base_Start_IT(&g_tim6_handle);                                      // 使能更新中断,并启动计数器

    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();

    // 测试代码
    static int32_t col_dsc[] = {70, 70, 70, LV_GRID_TEMPLATE_LAST};
    static int32_t row_dsc[] = {50, 50, 50, LV_GRID_TEMPLATE_LAST};

    lv_obj_t * cont = lv_obj_create(lv_screen_active());
    lv_obj_set_style_grid_column_dsc_array(cont, col_dsc, 0);
    lv_obj_set_style_grid_row_dsc_array(cont, row_dsc, 0);
    lv_obj_set_size(cont, 300, 220);
    lv_obj_center(cont);
    lv_obj_set_layout(cont, LV_LAYOUT_GRID);

    lv_obj_t * label;
    lv_obj_t * obj;

    uint32_t i;
    for(i = 0; i < 9; i++) 
    {
        uint8_t col = i % 3;
        uint8_t row = i / 3;

        obj = lv_button_create(cont);
        lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, col, 1, LV_GRID_ALIGN_STRETCH, row, 1);

        label = lv_label_create(obj);
        lv_label_set_text_fmt(label, "c%d, r%d", col, row);
        lv_obj_center(label);
    }

    while (1)
    {
        lv_timer_handler();
        Delay_ms(5);
    }
  
    return 0;
}
posted @ 2024-09-02 20:58  星光樱梦  阅读(8)  评论(0编辑  收藏  举报