20、鸿蒙/布局/创建网格 (Grid/GridItem)

作者: 圆梦人生 | 来源:发表于2024-07-16 09:03 被阅读0次

    概述

    网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器等。

    ArkUI提供了Grid容器组件和子组件GridItem,用于构建网格布局。Grid用于设置网格布局相关参数,GridItem定义子组件相关特征。Grid组件支持使用条件渲染、循环渲染、懒加载等方式生成子组件。

    布局与约束

    Grid组件为网格容器,其中容器内各条目对应一个GridItem组件,如下图所示。
    Grid的子组件必须是GridItem组件。


    Grid.png

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

    Grid.png

    如果Grid组件设置了宽高属性,则其尺寸为设置值。如果没有设置宽高属性,Grid组件的尺寸默认适应其父组件的尺寸
    Grid组件根据行列数量与占比属性的设置,可以分为三种布局情况:

    • 行、列数量与占比同时设置:Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。(推荐使用该种布局方式)
    • 只设置行、列数量与占比中的一个:元素按照设置的方向进行排布,超出的元素可通过滚动的方式展示。
    • 行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且Grid不可滚动。

    设置排列方式

    设置行列数量与占比

    通过设置行列数量与尺寸占比可以确定网格布局的整体排列方式。Grid组件提供了rowsTemplate和columnsTemplate属性用于设置网格布局行列数量与尺寸占比。

    rowsTemplate和columnsTemplate属性值是一个由多个空格和'数字+fr'间隔拼接的字符串,fr的个数即网格布局的行或列数,fr前面的数值大小,用于计算该行或列在网格布局宽度上的占比,最终决定该行或列宽度。

    Grid.png

    如上图所示,构建的是一个三行三列的网格布局,其在垂直方向上分为三等份,每行占一份;在水平方向上分为四等份,第一列占一份,第二列占两份,第三列占一份。

    只要将rowsTemplate的值为'1fr 1fr 1fr',同时将columnsTemplate的值为'1fr 2fr 1fr',即可实现上述网格布局。
    当Grid组件设置了rowsTemplate或columnsTemplate时,Grid的layoutDirection、maxCount、minCount、cellLength属性不生效,属性说明可参考Grid-属性

    @Component
    export struct GridLayout {
      build() {
        Grid(){
          GridItem(){
            Text('1')
          }
          GridItem(){
            Text('2')
          }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
          GridItem(){
            Text('3')
          }
          GridItem(){
            Text('4')
          }
          GridItem(){
            Text('5')
          }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
          GridItem(){
            Text('6')
          }
          GridItem(){
            Text('7')
          }
          GridItem(){
            Text('8')
          }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
          GridItem(){
            Text('9')
          }
        }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
        .rowsTemplate('1fr 1fr 1fr')
        .columnsTemplate('1fr 2fr 1fr')
      }
    }
    
    设置子组件所占行列数

    除了大小相同的等比例网格布局,由不同大小的网格组成不均匀分布的网格布局场景在实际应用中十分常见,如下图所示。在Grid组件中,可以通过创建Grid时传入合适的GridLayoutOptions实现如图所示的单个网格横跨多行或多列的场景,其中,irregularIndexes和onGetIrregularSizeByIndex可对仅设置rowsTemplate或columnsTemplate的Grid使用;onGetRectByIndex可对同时设置rowsTemplate和columnsTemplate的Grid使用。

    不均匀网格布局.png

    例如计算器的按键布局就是常见的不均匀网格布局场景。如下图,计算器中的按键“0”和“=”,按键“0”横跨第一、二两列,按键“=”横跨第五、六两行。使用Grid构建的网格布局,其行列标号从0开始,依次编号。

    计算器.png

    在网格中,可以通过onGetRectByIndex返回的[rowStart,columnStart,rowSpan,columnSpan]来实现跨行跨列布局,其中rowStart和columnStart属性表示指定当前元素起始行号和起始列号,rowSpan和columnSpan属性表示指定当前元素的占用行数和占用列数。

    @Component
    export struct GridLayout {
      //
      @State layoutOptions: GridLayoutOptions = {
        regularSize: [1, 1],
        //
        onGetRectByIndex: (index: number)=>{
          console.log('index ==== ', index)
          // [rowStart, columnStart, rowSpan, columnSpan]
          //  行的数量,列的数量,合并的行,合并的列
          if (index == 16) {
            //  0的索引位置
            return [2, 0, 0, 2]
          }
          else if(index == 15){
            // 等号位置
            return [0, 2, 2, 0]
          }
          // 不合并列行
          return [0, 0, 0, 0]
        }
      }
      //
      build() {
       
        // 计算器布局
        Scroll(){
          Column(){
          Row(){
            Text('请输入').height(100)
          }
          Grid(undefined, this.layoutOptions){
            GridItem(){
              Text('CE')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('C')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('/')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('X')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('7')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('8')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('9')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('-')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('4')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('5')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('6')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('+')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('1')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('2')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('3')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('=')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('0')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            // GridItem(){
            //   Text('')
            // }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            GridItem(){
              Text('.')
            }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
            // GridItem(){
            //   Text('=')
            // }.borderColor(Color.Red).borderWidth(1).borderStyle(BorderStyle.Solid)
          }.columnsTemplate('1fr 1fr 1fr 1fr')
          .rowsTemplate('1fr 1fr 1fr 1fr 1fr 2fr')
    
        }
        }
      }
    }
    

    设置主轴方向

    使用Grid构建网格布局时,若没有设置行列数量与占比,可以通过layoutDirection设置网格布局的主轴方向,决定子组件的排列方式。此时可以结合minCount和maxCount属性来约束主轴方向上的网格数量。

    主轴方向.png

    当前layoutDirection设置为Row时,先从左到右排列,排满一行再排下一行。当前layoutDirection设置为Column时,先从上到下排列,排满一列再排下一列,如上图所示。此时,将maxCount属性设为2,表示主轴方向上最大显示的网格单元数量为2。

    Grid(){
          GridItem(){
            Text('1')
          }
          GridItem(){
            Text('2')
          }
          GridItem(){
            Text('3')
          }
          GridItem(){
            Text('4')
          }
          GridItem(){
            Text('5')
          }
          GridItem(){
            Text('6')
          }
        }.maxCount(2)
        //
        .layoutDirection(GridDirection.Column)
    
    • layoutDirection属性仅在不设置rowsTemplate和columnsTemplate时生效,此时元素在layoutDirection方向上排列。
    • 仅设置rowsTemplate时,Grid主轴为水平方向,交叉轴为垂直方向。
    • 仅设置columnsTemplate时,Grid主轴为垂直方向,交叉轴为水平方向。

    在网格布局中显示数据

    网格布局采用二维布局的方式组织其内部元素,如下图所示。

    通用办公服务.png

    Grid组件可以通过二维布局的方式显示一组GridItem子组件。

    Grid(){
          GridItem(){
            Text("会议")
          }
          GridItem(){
            Text("签到")
          }
          GridItem(){
            Text("投票")
          }
          GridItem(){
            Text("打印")
          }
    }.rowsTemplate('1fr 1fr').columnsTemplate('1fr 1fr')
    

    对于内容结构相似的多个GridItem,通常更推荐使用ForEach语句中嵌套GridItem的形式,来减少重复代码。

    @State services: Array<string> = ['会议', '投票', '签到', '打印']
    ...
    Grid(){
          ForEach(this.services, (item: string, index:number)=>{
            GridItem(){
              Text(item)
            }
          })
    }.rowsTemplate('1fr 1fr').columnsTemplate('1fr 1fr')
    

    设置行列间距

    在两个网格单元之间的网格横向间距称为行间距,网格纵向间距称为列间距,如下图所示。

    间距.png

    通过Grid的rowsGap和columnsGap可以设置网格布局的行列间距。在图5所示的计算器中,行间距为20vp,列间距为10vp。

     Grid(){
          GridItem(){
            Text("会议")
          }.backgroundColor(Color.Red)
          GridItem(){
            Text("签到")
          }.backgroundColor(Color.Gray)
          GridItem(){
            Text("投票")
          }.backgroundColor(Color.Green)
          GridItem(){
            Text("打印")
          }.backgroundColor(Color.Orange)
        }.rowsTemplate('1fr 1fr')
        .columnsTemplate('1fr 1fr')
        .columnsGap(10) // 列间距
        .rowsGap(20)    // 行间距
    

    构建可滚动的网格布局

    可滚动的网格布局常用在文件管理、购物或视频列表等页面中,如下图所示。在设置Grid的行列数量与占比时,如果仅设置行、列数量与占比中的一个,即仅设置rowsTemplate或仅设置columnsTemplate属性,网格单元按照设置的方向排列,超出Grid显示区域后,Grid拥有可滚动能力。


    可滚动的网格布局.png

    如果设置的是columnsTemplate,Grid的滚动方向为垂直方向;如果设置的是rowsTemplate,Grid的滚动方向为水平方向。

    如上图所示的横向可滚动网格布局,只要设置rowsTemplate属性的值且不设置columnsTemplate属性,当内容超出Grid组件宽度时,Grid可横向滚动进行内容展示。

    Column({space: 5}){
          Grid(){
            GridItem(){
              Text("会议")
            }.backgroundColor(Color.Red).width('25%')
            GridItem(){
              Text("签到")
            }.backgroundColor(Color.Gray).width('25%')
            GridItem(){
              Text("投票")
            }.backgroundColor(Color.Green).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
            GridItem(){
              Text("打印")
            }.backgroundColor(Color.Orange).width('25%')
          }.rowsTemplate('1fr 1fr').rowsGap(15)
    }
    

    控制滚动位置

    与新闻列表的返回顶部场景类似,控制滚动位置功能在网格布局中也很常用,例如下图所示日历的翻页功能。

    日历.png
    @State calArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, ]
    
    Column(){
            // 日历
            Grid(){
              ForEach(this.calArr, (item: number, index: number)=>{
                GridItem(){
                  Text(item.toString())
                }.height(50)
              })
            }.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
            .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
    }
    

    相关文章

      网友评论

        本文标题:20、鸿蒙/布局/创建网格 (Grid/GridItem)

        本文链接:https://www.haomeiwen.com/subject/ygpzcjtx.html