首页 > 编程笔记 > 通用技能 阅读:6

ArkUI Grid网格布局详解(附带实例)

网格布局由行和列组成的单元格构成,开发者可以通过指定项目所在的单元格来实现各种布局。它具备强大的页面均分能力和子组件占比控制能力,是一种重要的自适应布局方案。常见的应用场景包括九宫格图片展示、日历布局、计算器等。

ArkUI 提供了 Grid 容器组件和 GridItem 子组件,用于构建网格布局:
Grid 组件支持使用条件渲染、循环渲染、懒加载等方式生成子组件。

Grid 组件为网格容器,容器内各条目对应一个 GridItem 组件,如下图所示。


图 1 Grid 与 GridItem 组件的关系

Grid 的子组件必须是 GridItem 组件。

网格布局是一种二维布局。Grid 组件支持自定义行列数和每行每列尺寸占比、设置子组件横跨几行或几列,同时提供了垂直和水平布局能力。当网格容器组件尺寸发生变化时,所有子组件及间距会等比例调整,从而实现网格布局的自适应能力。

根据 Grid 的这些布局能力,可以构建出不同样式的网格布局,如下图所示:


图 2 不同样式的网格布局

Grid网格布局

如果 Grid 组件设置了宽高属性,则其尺寸为设置值。如果 Grid 组件没有设置宽高属性,则其尺寸默认适应父组件的尺寸。

Grid 组件根据行列数量与尺寸占比属性的设置,可以分为 3 种布局情况:
通过设置行列数量与尺寸占比,可以确定网格布局的整体排列方式。Grid 组件提供了 rowsTemplate 和 columnsTemplate 属性来设置网格布局行列数量与尺寸占比。

rowsTemplate 和 columnsTemplate 属性值是一个由多个空格和'数字+fr'间隔拼接的字符串,其中:
行列数量与尺寸占比示例如下图所示:


图 3 行列数量与尺寸占比示例

在图 3 中,构建的是一个 3 行 3 列的网格布局,其在垂直方向上分为 3 等份,每行占 1 份;在水平方向上分为 4 等份,第一列占 1 份,第二列占 2 份,第三列占 1 份。只要将 rowsTemplate 的值设置为'1fr 1fr 1fr',同时将 columnsTemplate 的值设置为'1fr 2fr 1fr',即可实现上述网格布局,示例代码如下:
// 声明背景色数组
let colors: Color[] = [
  Color.Black, Color.Blue, Color.Brown,
  Color.Gray, Color.Green, Color.Grey,
  Color.Orange, Color.Pink, Color.Red
];

@Entry
@Component
struct GridSample {
  build() {
    // 网格布局组件
    Grid() {
      ForEach(colors, (color: Color, index: number) => {
        // 网格布局单元组件
        GridItem()
          .backgroundColor(color)
      }, (index: number) => index.toString())
    }
    // 3 行等高划分
    .rowsTemplate('1fr 1fr 1fr')
    // 第一列和第三列占据一个单位的宽度,第二列占据两个单位的宽度
    .columnsTemplate('1fr 2fr 1fr')
  }
}
当 Grid 组件设置了 rowsTemplate 或 columnsTemplate 时,Grid 的 layoutDirection、maxCount、minCount、cellLength 属性不生效。

显示效果如下图所示:


图 4 网格布局显示效果

Grid不均匀网格布局

除了大小相同的等比例网格布局之外,由不同大小的网格组成的不均匀分布的网格布局在实际应用中也十分常见。

可以通过在创建 Grid 时传入合适的 GridLayoutOptions,实现如下图所示的单个网格横跨多行或多列的场景。


图 5 子组件跨行列显示效果

其中,irregularIndexes 和 onGetIrregularSizeByIndex 用于单独设置 rowsTemplate 或 columnsTemplate 的 Grid,onGetRectByIndex 用于同时设置 rowsTemplate 和 columnsTemplate 的 Grid。

在网格中,可以通过 onGetRectByIndex 返回的 [rowStart,columnStart,rowSpan,columnSpan] 来实现跨行跨列布局,其中 rowStart 和 columnStart 属性指定当前元素起始行号和起始列号,rowSpan 和 columnSpan 属性指定当前元素的占用行数和占用列数。使用 Grid 构建的网格布局,其行列标号从 0 开始,依次增加。

计算器的按键布局就是常见的不均匀网格布局场景,如下图所示,按键“0”横跨第一、二两列,按键“=”横跨第五、六两行。


图 6 不均匀网格布局显示效果示意图

只需将按键“0”对应的 onGetRectByIndex 的 rowStart 和 columnStart 分别设为 5 和 0,rowSpan 和 columnSpan 分别设为 1 和 2,将按键“=”对应的 onGetRectByIndex 的 rowStart 和 columnStart 分别设为 4 和 3,rowSpan 和 columnSpan 分别设为 2 和 1 即可。

示例代码如下:
@Entry
@Component
struct GridSample {
  // 布局选项
  layoutOptions: GridLayoutOptions = {
    regularSize: [1, 1], // 绝大多数网格的尺寸,通常是占据 1 行 1 列
    onGetRectByIndex: (index: number) => {
      switch (index) {
        // 返回值格式:[rowStart, columnStart, rowSpan, columnSpan]
        case 1: return [1, 0, 1, 1]; // 从 [1, 0] 开始,跨行 1,跨列 1
        case 2: return [1, 1, 1, 1]; // 从 [1, 1] 开始,跨行 1,跨列 1
        // ...
      }
    }
  }

  // 显示的文本数组
  texts: string[] = [
    'CE', 'C', '/', '×',
    '7', '8', '9', '-',
    '4', '5', '6', '+',
    '1', '2', '3', '=',
    '0'
  ];

  build() {
    // 设置使用指定的布局选项 layoutOptions
    Grid(undefined, this.layoutOptions) {
      GridItem() {
        Text('0')
          .width('100%')
          .height('100%')
          .backgroundColor('#CCCCCC')
          .padding(5)
          .fontSize(30)
          .textAlign(TextAlign.End)
          .borderRadius(5)
      }
      .padding(5)

      ForEach(this.texts, (text: string, index: number) => {
        GridItem() {
          Button(text)
            .type(ButtonType.Normal)
            .height('100%')
            .width('100%')
            .backgroundColor('#AAAAAA')
            .borderRadius(5)
            .fontSize(20)
        }
        .padding(5)
      }, (index: number) => index.toString())
    }
    // 设置 4 列等宽
    .columnsTemplate('1fr 1fr 1fr 1fr')
    // 设置 6 行:第一行占据两个单位高度,其余每行占据一个单位高度
    .rowsTemplate('2fr 1fr 1fr 1fr 1fr 1fr')
    .height('70%')
    .width('100%')
    .backgroundColor('#EEEEEE')
    .padding(5)
  }
}

Grid设置主轴方向

使用 Grid 构建网格布局时,若没有设置行列数量与尺寸占比,可以通过 layoutDirection 设置网格布局的主轴方向,决定子组件的排列方式:
主轴方向的示意图如下图所示:


图 7 主轴方向示意图

此时可以结合 minCount 和 maxCount 属性来约束主轴方向上的网格数量。例如将 maxCount 属性设置为 3,表示主轴方向上显示的最大网格单元数量为 3。

图 7 中的 4 种布局实现的示例代码如下:
@Entry
@Component
struct GridSample {
  // 显示的文本数组
  texts: string[] = [
    '1', '2', '3', '4', '5', '6', '7', '8', '9'
  ];

  build() {
    Row() {
      Grid() {
        ForEach(this.texts, (text: string, index: number) => {
          GridItem() {
            Text(text)
              .height(100)
              .width(100)
              .backgroundColor('#AAAAAA')
              .borderRadius(5)
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
          }
          .padding(5)
        }, (index: number) => index.toString())
      }
      // 注意:如果设置了多个 layoutDirection,则最后一次设置的生效
      // 设置主轴方向为行的从左向右
      .layoutDirection(GridDirection.Row)
      // 设置主轴方向为从上向下
      .layoutDirection(GridDirection.Column)
      // 设置主轴方向为从右向左
      .layoutDirection(GridDirection.RowReverse)
      // 设置主轴方向为从下向上
      .layoutDirection(GridDirection.ColumnReverse)

      // 设置主轴方向最多显示 3 个网格单元
      .maxCount(3)
    }
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#EEEEEE')
    .width('100%')
    .height('50%')
  }
}

相关文章