• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![zh-cn_image_0000001511900472](figures/zh-cn_image_0000001511900472.png)
26
27>**说明:**
28>
29>Grid的子组件必须是GridItem组件。
30
31网格布局是一种二维布局。Grid组件支持自定义行列数和每行每列尺寸占比、设置子组件横跨几行或者几列,同时提供了垂直和水平布局能力。当网格容器组件尺寸发生变化时,所有子组件以及间距会等比例调整,从而实现网格布局的自适应能力。根据Grid的这些布局能力,可以构建出不同样式的网格布局,如下图所示。
32
33  **图2** 网格布局  
34![zh-cn_image_0000001562700473](figures/zh-cn_image_0000001562700473.png)
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![zh-cn_image_0000001562820833](figures/zh-cn_image_0000001562820833.png)
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![zh-cn_image_0000001511900480](figures/zh-cn_image_0000001511900480.png)
84
85例如计算器的按键布局就是常见的不均匀网格布局场景。如下图,计算器中的按键“0”和“=”,按键“0”横跨第一、二两列,按键“=”横跨第五、六两行。使用Grid构建的网格布局,其行列标号从0开始,依次编号。
86
87  **图5** 计算器  
88
89![zh-cn_image_0000001511421292](figures/zh-cn_image_0000001511421292.png)
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![zh-cn_image_0000001562700469](figures/zh-cn_image_0000001562700469.png)
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![zh-cn_image_0000001563060729](figures/zh-cn_image_0000001563060729.png)
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![zh-cn_image_0000001511580908](figures/zh-cn_image_0000001511580908.png)
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![zh-cn_image_0000001511740512](figures/zh-cn_image_0000001511740512.gif)
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![zh-cn_image_0000001562940549](figures/zh-cn_image_0000001562940549.gif)
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![ScrollBar](figures/grid_scrollbar.gif)
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-->