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

概述

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

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')
}
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容