1# 创建网格 (Grid/GridItem) 2 3<!--Kit: ArkUI--> 4<!--Subsystem: ArkUI--> 5<!--Owner: @zcdqs; @fangyuhao--> 6<!--Designer: @zcdqs--> 7<!--Tester: @liuzhenshuo--> 8<!--Adviser: @HelloCrease--> 9 10## 概述 11 12网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器等。 13 14ArkUI提供了[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)容器组件和子组件[GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md),用于构建网格布局。Grid用于设置网格布局相关参数,GridItem定义子组件相关特征。Grid组件支持使用[条件渲染](../ui/state-management/arkts-rendering-control-ifelse.md)、[循环渲染](../ui/state-management/arkts-rendering-control-foreach.md)、[懒加载](../ui/state-management/arkts-rendering-control-lazyforeach.md)等方式生成子组件。 15 16> **说明:** 17> 18> 本文仅展示关键代码片段,可运行的完整代码请参考<!--RP2-->[创建网格代码](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/DocsSample/ArkUISample/ScrollableComponent)<!--RP2End-->。 19 20## 布局与约束 21 22Grid组件为网格容器,其中容器内各条目对应一个GridItem组件,如下图所示。 23 24 **图1** Grid与GridItem组件关系 25 26 27>**说明:** 28> 29>Grid的子组件必须是GridItem组件。 30 31网格布局是一种二维布局。Grid组件支持自定义行列数和每行每列尺寸占比、设置子组件横跨几行或者几列,同时提供了垂直和水平布局能力。当网格容器组件尺寸发生变化时,所有子组件以及间距会等比例调整,从而实现网格布局的自适应能力。根据Grid的这些布局能力,可以构建出不同样式的网格布局,如下图所示。 32 33 **图2** 网格布局 34 35 36如果Grid组件设置了宽高属性,则其尺寸为设置值。如果没有设置宽高属性,Grid组件的尺寸默认适应其父组件的尺寸。 37 38Grid组件根据行列数量与占比属性的设置,可以分为三种布局情况: 39 40- 行、列数量与占比同时设置:Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。(推荐使用该种布局方式) 41 42- 只设置行、列数量与占比中的一个:元素按照设置的方向进行排布,超出的元素可通过滚动的方式展示。 43 44- 行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且Grid不可滚动。 45 46 47## 设置排列方式 48 49 50### 设置行列数量与占比 51 52通过设置行列数量与尺寸占比可以确定网格布局的整体排列方式。Grid组件提供了rowsTemplate和columnsTemplate属性用于设置网格布局行列数量与尺寸占比。 53 54rowsTemplate和columnsTemplate属性值是一个由多个空格和'数字+fr'间隔拼接的字符串,fr的个数即网格布局的行或列数,fr前面的数值大小,用于计算该行或列在网格布局宽度上的占比,最终决定该行或列宽度。 55 56 **图3** 行列数量占比示例 57 58 59如上图所示,构建的是一个三行三列的网格布局,其在垂直方向上分为三等份,每行占一份;在水平方向上分为四等份,第一列占一份,第二列占两份,第三列占一份。 60 61只要将rowsTemplate设置为'1fr 1fr 1fr',同时将columnsTemplate设置为'1fr 2fr 1fr',即可实现上述网格布局。 62 63 64```ts 65Grid() { 66 // ... 67} 68.rowsTemplate('1fr 1fr 1fr') 69.columnsTemplate('1fr 2fr 1fr') 70``` 71 72>**说明:** 73> 74>当Grid组件设置了rowsTemplate或columnsTemplate时,Grid的layoutDirection、maxCount、minCount、cellLength属性不生效,属性说明可参考[Grid-属性](../reference/apis-arkui/arkui-ts/ts-container-grid.md#属性)。 75 76 77### 设置子组件所占行列数 78 79除了大小相同的等比例网格布局,由不同大小的网格组成不均匀分布的网格布局场景在实际应用中十分常见,如下图所示。在Grid组件中,可以通过创建Grid时传入合适的[GridLayoutOptions](../reference/apis-arkui/arkui-ts/ts-container-grid.md#gridlayoutoptions10对象说明)实现如图所示的单个网格横跨多行或多列的场景,其中,irregularIndexes和onGetIrregularSizeByIndex可对仅设置rowsTemplate或columnsTemplate的Grid使用;onGetRectByIndex可对同时设置rowsTemplate和columnsTemplate的Grid使用。 80 81 **图4** 不均匀网格布局 82 83 84 85例如计算器的按键布局就是常见的不均匀网格布局场景。如下图,计算器中的按键“0”和“=”,按键“0”横跨第一、二两列,按键“=”横跨第五、六两行。使用Grid构建的网格布局,其行列标号从0开始,依次编号。 86 87 **图5** 计算器 88 89 90 91在网格中,可以通过onGetRectByIndex返回的[rowStart,columnStart,rowSpan,columnSpan]来实现跨行跨列布局,其中rowStart和columnStart属性表示指定当前元素起始行号和起始列号,rowSpan和columnSpan属性表示指定当前元素的占用行数和占用列数。 92 93所以“0”按键横跨第一列和第二列,“=”按键横跨第五行和第六行,只要将“0”对应onGetRectByIndex的rowStart和columnStart设为6和0,rowSpan和columnSpan设为1和2,将“=”对应onGetRectByIndex的rowStart和columnStart设为5和3,rowSpan和columnSpan设为2和1即可。 94 95 96```ts 97layoutOptions: GridLayoutOptions = { 98 regularSize: [1, 1], 99 onGetRectByIndex: (index: number) => { 100 if (index == key1) { // key1是“0”按键对应的index 101 return [6, 0, 1, 2]; 102 } else if (index == key2) { // key2是“=”按键对应的index 103 return [5, 3, 2, 1]; 104 } 105 // ... 106 // 这里需要根据具体布局返回其他item的位置 107 } 108} 109 110Grid(undefined, this.layoutOptions) { 111 // ... 112} 113.columnsTemplate('1fr 1fr 1fr 1fr') 114.rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr') 115``` 116 117 118### 设置主轴方向 119 120使用Grid构建网格布局时,若没有设置行列数量与占比,可以通过layoutDirection设置网格布局的主轴方向,决定子组件的排列方式。此时可以结合minCount和maxCount属性来约束主轴方向上的网格数量。 121 122 **图6** 主轴方向示意图 123 124 125 126当前layoutDirection设置为Row时,先从左到右排列,排满一行再排下一行。当前layoutDirection设置为Column时,先从上到下排列,排满一列再排下一列,如上图所示。此时,将maxCount属性设为3,表示主轴方向上最大显示的网格单元数量为3。 127 128 129```ts 130Grid() { 131 // ... 132} 133.maxCount(3) 134.layoutDirection(GridDirection.Row) 135``` 136 137>**说明:** 138> 139>- layoutDirection属性仅在不设置rowsTemplate和columnsTemplate时生效,此时元素在layoutDirection方向上排列。 140>- 仅设置rowsTemplate时,Grid主轴为水平方向,交叉轴为垂直方向。 141>- 仅设置columnsTemplate时,Grid主轴为垂直方向,交叉轴为水平方向。 142 143 144## 在网格布局中显示数据 145 146网格布局采用二维布局的方式组织其内部元素,如下图所示。 147 148**图7** 通用办公服务 149 150 151 152Grid组件可以通过二维布局的方式显示一组GridItem子组件。 153 154 155```ts 156Grid() { 157 GridItem() { 158 Text('会议') 159 // ... 160 } 161 162 GridItem() { 163 Text('签到') 164 // ... 165 } 166 167 GridItem() { 168 Text('投票') 169 // ... 170 } 171 172 GridItem() { 173 Text('打印') 174 // ... 175 } 176} 177.rowsTemplate('1fr 1fr') 178.columnsTemplate('1fr 1fr') 179``` 180 181对于内容结构相似的多个GridItem,通常更推荐使用ForEach语句中嵌套GridItem的形式,来减少重复代码。 182 183 184```ts 185@Entry 186@Component 187struct OfficeService { 188 @State services: Array<string> = ['会议', '投票', '签到', '打印']; 189 190 build() { 191 Column() { 192 Grid() { 193 ForEach(this.services, (service:string) => { 194 GridItem() { 195 Text(service) 196 } 197 }, (service:string):string => service) 198 } 199 .rowsTemplate(('1fr 1fr') as string) 200 .columnsTemplate(('1fr 1fr') as string) 201 } 202 } 203} 204``` 205 206 207## 设置行列间距 208 209在两个网格单元之间的网格横向间距称为行间距,网格纵向间距称为列间距,如下图所示。 210 211**图8** 网格的行列间距 212 213 214 215通过Grid的rowsGap和columnsGap可以设置网格布局的行列间距。在图5所示的计算器中,行间距为15vp,列间距为10vp。 216 217 218```ts 219Grid() { 220 // ... 221} 222.columnsGap(10) 223.rowsGap(15) 224``` 225 226 227## 构建可滚动的网格布局 228 229可滚动的网格布局常用在文件管理、购物或视频列表等页面中,如下图所示。在设置Grid的行列数量与占比时,如果仅设置行、列数量与占比中的一个,即仅设置rowsTemplate或仅设置columnsTemplate属性,网格单元按照设置的方向排列,超出Grid显示区域后,Grid拥有可滚动能力。 230 231**图9** 横向可滚动网格布局 232 233 234 235如果设置的是columnsTemplate,Grid的滚动方向为垂直方向;如果设置的是rowsTemplate,Grid的滚动方向为水平方向。 236 237如上图所示的横向可滚动网格布局,只要设置rowsTemplate属性的值且不设置columnsTemplate属性,当内容超出Grid组件宽度时,Grid可横向滚动进行内容展示。 238 239 240```ts 241@Entry 242@Component 243struct Shopping { 244 @State services: Array<string> = ['直播', '进口']; 245 246 build() { 247 Column({ space: 5 }) { 248 Grid() { 249 ForEach(this.services, (service: string, index) => { 250 GridItem() { 251 } 252 .width('25%') 253 }, (service:string):string => service) 254 } 255 .rowsTemplate('1fr 1fr') // 只设置rowsTemplate属性,当内容超出Grid区域时,可水平滚动。 256 .rowsGap(15) 257 } 258 } 259} 260``` 261 262 263## 控制滚动位置 264 265与新闻列表的返回顶部场景类似,控制滚动位置功能在网格布局中也很常用,例如下图所示日历的翻页功能。 266 267 **图10** 日历翻页 268 269 270 271Grid组件初始化时,可以绑定一个[Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller)对象,用于进行滚动控制,例如通过Scroller对象的[scrollPage](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scrollpage9)方法进行翻页。 272 273 274```ts 275private scroller: Scroller = new Scroller(); 276``` 277 278在日历页面中,用户在点击“下一页”按钮时,应用响应点击事件,通过指定scrollPage方法的参数next为true,滚动到下一页。 279 280 281```ts 282Column({ space: 5 }) { 283 Grid(this.scroller) { 284 } 285 .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr') 286 287 Row({ space: 20 }) { 288 Button('上一页') 289 .onClick(() => { 290 this.scroller.scrollPage({ 291 next: false 292 }); 293 }) 294 295 Button('下一页') 296 .onClick(() => { 297 this.scroller.scrollPage({ 298 next: true 299 }); 300 }) 301 } 302} 303``` 304 305 306## 添加外置滚动条 307 308网格组件[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)可与[ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md)组件配合使用,为网格添加外置滚动条。两者通过绑定同一个[Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller)滚动控制器对象实现联动。 309 3101. 首先,需要创建一个[Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller)类型的对象gridScroller。 311 312 ```ts 313 private gridScroller: Scroller = new Scroller(); 314 ``` 315 3162. 然后,通过[scroller](../reference/apis-arkui/arkui-ts/ts-container-grid.md#接口)参数绑定滚动控制器。 317 318 ```ts 319 // gridScroller初始化Grid组件的scroller参数,绑定gridScroller与网格。 320 Grid({ scroller: this.gridScroller }) { 321 // ... 322 } 323 ``` 324 3253. 最后,滚动条通过[scroller](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md#scrollbaroptions对象说明)参数绑定滚动控制器。 326 327 ```ts 328 // gridScroller初始化ScrollBar组件的scroller参数,绑定gridScroller与滚动条。 329 ScrollBar({ scroller: this.gridScroller }) 330 ``` 331 332 **图11** 网格的外置滚动条 333 334 335 336>**说明:** 337>- 滚动条组件[ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md),还可配合其他可滚动组件使用,如[ArcList](../reference/apis-arkui/arkui-ts/ts-container-arclist.md)、[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)、[Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md)、[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)。 338>- 在圆形屏幕设备上,[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)可以与弧形滚动条组件[ArcScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-arcscrollbar.md)配合使用为网格添加弧形外置滚动条,使用方式可参考[创建弧形列表 (ArcList)](./arkts-layout-development-create-arclist.md)的[添加外置滚动条ArcScrollBar](./arkts-layout-development-create-arclist.md#添加外置滚动条arcscrollbar)章节。 339 340## 性能优化 341 342与[长列表的处理](arkts-layout-development-create-list.md#长列表的处理)类似,[循环渲染](../ui/state-management/arkts-rendering-control-foreach.md)适用于数据量较小的布局场景,当构建具有大量网格项的可滚动网格布局时,推荐使用[数据懒加载](../ui/state-management/arkts-rendering-control-lazyforeach.md)方式实现按需迭代加载数据,从而提升网格性能。 343 344关于按需加载优化的具体实现可参考[数据懒加载](../ui/state-management/arkts-rendering-control-lazyforeach.md)章节中的示例。 345 346当使用懒加载方式渲染网格时,为了更好的滚动体验,减少滑动时出现白块,Grid组件中也可通过cachedCount属性设置GridItem的预加载数量,只在懒加载LazyForEach中生效。 347 348 设置预加载数量后,会在Grid显示区域前后各缓存cachedCount\*列数个GridItem,超出显示和缓存范围的GridItem会被释放。 349 350```ts 351Grid() { 352 LazyForEach(this.dataSource, () => { 353 GridItem() { 354 } 355 }) 356} 357.cachedCount(3) 358``` 359 360>**说明:** 361> 362>cachedCount的增加会增大UI的CPU、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。 363 364## 相关实例 365 366针对网格开发,有以下相关实例可供参考: 367 368- [游戏2048(ArkTS)(API9)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/Solutions/Game/Game2048) 369 370- [分布式计算器](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/ArkTSDistributedCalc) 371<!--RP1--><!--RP1End-->