• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Building a Food Category Grid Layout
2
3
4The diet application allows food on the home page to display in list or grid mode. You can implement switching between food categories through tabs in grid mode.
5
6
71. Import the Category enumeration type to the FoodCategoryList page.
8
9   ```
10   import { Category, FoodData } from '../model/FoodData'
11   ```
12
132. Create the FoodCategoryList and FoodCategory components. The FoodCategoryList component is used as the entry component of the new page, and the initializeOnStartup method is invoked in the entry component.
14
15   ```
16   @Component
17   struct FoodList {
18     private foodItems: FoodData[]
19     build() {
20       ......
21     }
22   }
23
24   @Component
25   struct FoodCategory {
26     private foodItems: FoodData[]
27     build() {
28       ......
29     }
30   }
31
32   @Entry
33   @Component
34   struct FoodCategoryList {
35     private foodItems: FoodData[] = initializeOnStartup()
36     build() {
37       ......
38     }
39   }
40   ```
41
423. Create the showList member variable in the FoodCategoryList component to control the rendering switchover between the list layout and grid layout. The conditional rendering statement if...else... is required.
43
44   ```
45   @Entry
46   @Component
47   struct FoodCategoryList {
48     private foodItems: FoodData[] = initializeOnStartup()
49     private showList: boolean = false
50
51     build() {
52       Stack() {
53         if (this.showList) {
54           FoodList({ foodItems: this.foodItems })
55         } else {
56           FoodCategory({ foodItems: this.foodItems })
57         }
58       }
59     }
60   }
61   ```
62
634. In the upper right corner of the page, create an icon for switching between the list and grid layouts. Set the stack alignment mode to TopEnd, top-bottom alignment. Create an image component, and set the click event, that is, negation of showList.
64
65   ```
66   @Entry
67   @Component
68   struct FoodCategoryList {
69     private foodItems: FoodData[] = initializeOnStartup()
70     private showList: boolean = false
71
72     build() {
73       Stack({ alignContent: Alignment.TopEnd }) {
74         if (this.showList) {
75           FoodList({ foodItems: this.foodItems })
76         } else {
77           FoodCategory({ foodItems: this.foodItems })
78         }
79         Image($r('app.media.Switch'))
80           .height(24)
81           .width(24)
82           .margin({ top: 15, right: 10 })
83           .onClick(() => {
84           this.showList = !this.showList
85         })
86       }.height('100%')
87     }
88   }
89   ```
90
915. Add the @State decorator. After you click the switch tab in the upper right corner, the page does not change. This is because the showList does not have state data and its change does not trigger the page refresh. You need to add the @State decorator to make it state data. The change of the @State decorator will cause re-rendering of the component where the decorator is located.
92
93   ```
94   @Entry
95   @Component
96   struct FoodCategoryList {
97     private foodItems: FoodData[] = initializeOnStartup()
98     @State private showList: boolean = false
99
100     build() {
101       Stack({ alignContent: Alignment.TopEnd }) {
102         if (this.showList) {
103           FoodList({ foodItems: this.foodItems })
104         } else {
105           FoodCategory({ foodItems: this.foodItems })
106         }
107         Image($r('app.media.Switch'))
108           .height(24)
109           .width(24)
110           .margin({ top: 15, right: 10 })
111           .onClick(() => {
112           this.showList = !this.showList
113         })
114       }.height('100%')
115     }
116   }
117
118   ```
119
120   When you click the switch icon, the FoodList component is displayed. When you click the switch icon again, the FoodList component is hidden.
121
122   ![en-us_image_0000001222807800](figures/en-us_image_0000001222807800.gif)
123
1246. Create a tab to display all food categories (All). Create the \<Tabs> component and its child component TabContent in the FoodCategory component, and set tabBar to All. Set the width of the TabBars to 280 and the layout mode to Scrollable. This means that the TabBars can be scrolled when the total length exceeds 280. The \<Tabs> component is a container component that allows users to switch between content views through tabs. Each tab page corresponds to a TabContent.
125
126   ```
127   @Component
128   struct FoodCategory {
129     private foodItems: FoodData[]
130     build() {
131       Stack() {
132         Tabs() {
133           TabContent() {}.tabBar('All')
134         }
135         .barWidth(280)
136         .barMode(BarMode.Scrollable)
137       }
138     }
139   }
140   ```
141
142   ![en-us_image_0000001267647881](figures/en-us_image_0000001267647881.png)
143
1447. Create the FoodGrid component to function as a child component of the TabContent component.
145
146   ```
147   @Component
148   struct FoodGrid {
149     private foodItems: FoodData[]
150     build() {}
151   }
152
153   @Component
154   struct FoodCategory {
155     private foodItems: FoodData[]
156     build() {
157       Stack() {
158         Tabs() {
159           TabContent() {
160             FoodGrid({ foodItems: this.foodItems })
161           }.tabBar('All')
162         }
163         .barWidth(280)
164         .barMode(BarMode.Scrollable)
165       }
166     }
167   }
168   ```
169
1708. Implement a 2 x 6 grid layout (12 food data resources in total). Create a Grid component, and set columnsTemplate to ('1fr 1fr'), rowsTemplate to ('1fr 1fr 1fr 1fr 1fr 1fr'), and both rowsGap and columnsGap to 8. Create a Scroll component so that it can be slid.
171
172   ```
173   @Component
174   struct FoodGrid {
175     private foodItems: FoodData[]
176     build() {
177       Scroll() {
178         Grid() {
179           ForEach(this.foodItems, (item: FoodData) => {
180             GridItem() {}
181           }, (item: FoodData) => item.id.toString())
182         }
183         .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
184         .columnsTemplate('1fr 1fr')
185         .columnsGap(8)
186         .rowsGap(8)
187       }
188       .scrollBar(BarState.Off)
189       .padding({left: 16, right: 16})
190     }
191   }
192   ```
193
1949. Create a FoodGridItem component to display the food image, name, and calories and implement the UI layout. The FoodGridItem component is a child component of the GridItem component. The height of each FoodGridItem is 184, and the line spacing is 8. The total height of the Grid component is calculated as follows: (184 + 8) x 6 – 8 = 1144.
195
196   ```
197   @Component
198   struct FoodGridItem {
199     private foodItem: FoodData
200     build() {
201       Column() {
202         Row() {
203           Image(this.foodItem.image)
204             .objectFit(ImageFit.Contain)
205             .height(152)
206             .width('100%')
207         }.backgroundColor('#FFf1f3f5')
208         Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
209           Text(this.foodItem.name)
210             .fontSize(14)
211             .flexGrow(1)
212             .padding({ left: 8 })
213           Text(this.foodItem.calories + 'kcal')
214             .fontSize(14)
215             .margin({ right: 6 })
216         }
217         .height(32)
218         .width('100%')
219         .backgroundColor('#FFe5e5e5')
220       }
221       .height(184)
222       .width('100%')
223     }
224   }
225
226   @Component
227   struct FoodGrid {
228     private foodItems: FoodData[]
229     build() {
230       Scroll() {
231         Grid() {
232           ForEach(this.foodItems, (item: FoodData) => {
233             GridItem() {
234               FoodGridItem({foodItem: item})
235             }
236           }, (item: FoodData) => item.id.toString())
237         }
238         .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
239         .columnsTemplate('1fr 1fr')
240         .columnsGap(8)
241         .rowsGap(8)
242         .height(1144)
243       }
244       .scrollBar(BarState.Off)
245       .padding({ left: 16, right: 16 })
246     }
247   }
248   ```
249
250   ![en-us_image_0000001223287708](figures/en-us_image_0000001223287708.gif)
251
25210. Create the Category.Vegetable, Category.Fruit, Category.Nut, Category.SeaFood, and Category.Dessert tabs.
253
254   ```
255   @Component
256   struct FoodCategory {
257     private foodItems: FoodData[]
258     build() {
259       Stack() {
260         Tabs() {
261           TabContent() {
262             FoodGrid({ foodItems: this.foodItems })
263           }.tabBar('All')
264
265           TabContent() {
266             FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Vegetable)) })
267           }.tabBar('Vegetable')
268
269           TabContent() {
270             FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Fruit)) })
271           }.tabBar('Fruit')
272
273           TabContent() {
274             FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Nut)) })
275           }.tabBar('Nut')
276
277           TabContent() {
278             FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Seafood)) })
279           }.tabBar('Seafood')
280
281           TabContent() {
282             FoodGrid({ foodItems: this.foodItems.filter(item => (item.category === Category.Dessert)) })
283           }.tabBar('Dessert')
284         }
285         .barWidth(280)
286         .barMode(BarMode.Scrollable)
287       }
288     }
289   }
290   ```
291
29211. Set the number of rows and height of grids for different food categories. Because the number of foods varies according to the category, the ''1fr 1fr 1fr 1fr 1fr 1fr ' constant cannot be used to set the number of rows to 6.
293      Create member variables gridRowTemplate and HeightValue, and set the number of grid rows and height by using these member variables.
294
295
296   ```
297   @Component
298   struct FoodGrid {
299     private foodItems: FoodData[]
300     private gridRowTemplate : string = ''
301     private heightValue: number
302     build() {
303       Scroll() {
304         Grid() {
305           ForEach(this.foodItems, (item: FoodData) => {
306             GridItem() {
307               FoodGridItem({foodItem: item})
308             }
309           }, (item: FoodData) => item.id.toString())
310         }
311         .rowsTemplate(this.gridRowTemplate)
312         .columnsTemplate('1fr 1fr')
313         .columnsGap(8)
314         .rowsGap(8)
315         .height(this.heightValue)
316       }
317       .scrollBar(BarState.Off)
318       .padding({left: 16, right: 16})
319     }
320   }
321   ```
322
323   Invoke the aboutToAppear API to calculate the number of rows (gridRowTemplate) and height (heightValue).
324
325
326   ```
327   aboutToAppear() {
328     var rows = Math.round(this.foodItems.length / 2);
329     this.gridRowTemplate = '1fr '.repeat(rows);
330     this.heightValue = rows * 192 - 8;
331   }
332   ```
333
334   The custom component provides two lifecycle callbacks: aboutToAppear and aboutToDisappear. aboutToAppear is executed after the custom component is created and before the build method of the custom component is executed. aboutToDisappear is executed when the custom component is deinitialized.
335
336   ![en-us_image_0000001267647885](figures/en-us_image_0000001267647885.png)
337
338
339   ```
340   @Component
341   struct FoodGrid {
342     private foodItems: FoodData[]
343     private gridRowTemplate : string = ''
344     private heightValue: number
345
346     aboutToAppear() {
347       var rows = Math.round(this.foodItems.length / 2);
348       this.gridRowTemplate = '1fr '.repeat(rows);
349       this.heightValue = rows * 192 - 8;
350     }
351
352     build() {
353       Scroll() {
354         Grid() {
355           ForEach(this.foodItems, (item: FoodData) => {
356             GridItem() {
357               FoodGridItem({foodItem: item})
358             }
359           }, (item: FoodData) => item.id.toString())
360         }
361         .rowsTemplate(this.gridRowTemplate)
362         .columnsTemplate('1fr 1fr')
363         .columnsGap(8)
364         .rowsGap(8)
365         .height(this.heightValue)
366       }
367       .scrollBar(BarState.Off)
368       .padding({left: 16, right: 16})
369     }
370   }
371   ```
372
373   ![en-us_image_0000001267887869](figures/en-us_image_0000001267887869.gif)
374