• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Building a Food Category Grid Layout
2
3The 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.
4
5
61. Import the **Category** enumeration type to the **FoodCategoryList** page.
7
8   ```ts
9   import { Category, FoodData } from '../model/FoodData'
10   ```
11
122. 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.
13
14   ```ts
15   @Component
16   struct FoodList {
17     private foodItems: FoodData[]
18     build() {
19       ......
20     }
21   }
22
23   @Component
24   struct FoodCategory {
25     private foodItems: FoodData[]
26     build() {
27       ......
28     }
29   }
30
31   @Entry
32   @Component
33   struct FoodCategoryList {
34     private foodItems: FoodData[] = initializeOnStartup()
35     build() {
36       ......
37     }
38   }
39   ```
40
413. 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.
42
43   ```ts
44   @Entry
45   @Component
46   struct FoodCategoryList {
47     private foodItems: FoodData[] = initializeOnStartup()
48     private showList: boolean = false
49
50     build() {
51       Stack() {
52         if (this.showList) {
53           FoodList({ foodItems: this.foodItems })
54         } else {
55           FoodCategory({ foodItems: this.foodItems })
56         }
57       }
58     }
59   }
60   ```
61
624. 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**.
63
64   ```ts
65   @Entry
66   @Component
67   struct FoodCategoryList {
68     private foodItems: FoodData[] = initializeOnStartup()
69     private showList: boolean = false
70
71     build() {
72       Stack({ alignContent: Alignment.TopEnd }) {
73         if (this.showList) {
74           FoodList({ foodItems: this.foodItems })
75         } else {
76           FoodCategory({ foodItems: this.foodItems })
77         }
78         Image($r('app.media.Switch'))
79           .height(24)
80           .width(24)
81           .margin({ top: 15, right: 10 })
82           .onClick(() => {
83           this.showList = !this.showList
84         })
85       }.height('100%')
86     }
87   }
88   ```
89
905. 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.
91
92   ```ts
93   @Entry
94   @Component
95   struct FoodCategoryList {
96     private foodItems: FoodData[] = initializeOnStartup()
97     @State private showList: boolean = false
98
99     build() {
100       Stack({ alignContent: Alignment.TopEnd }) {
101         if (this.showList) {
102           FoodList({ foodItems: this.foodItems })
103         } else {
104           FoodCategory({ foodItems: this.foodItems })
105         }
106         Image($r('app.media.Switch'))
107           .height(24)
108           .width(24)
109           .margin({ top: 15, right: 10 })
110           .onClick(() => {
111           this.showList = !this.showList
112         })
113       }.height('100%')
114     }
115   }
116
117   ```
118
119   When you click the switch icon, the **FoodList** component is displayed. When you click the switch icon again, the **FoodList** component is hidden.
120
121   ![en-us_image_0000001170411978](figures/en-us_image_0000001170411978.gif)
122
1236. 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**.
124
125   ```ts
126   @Component
127   struct FoodCategory {
128     private foodItems: FoodData[]
129     build() {
130       Stack() {
131         Tabs() {
132           TabContent() {}.tabBar('All')
133         }
134         .barWidth(280)
135         .barMode(BarMode.Scrollable)
136       }
137     }
138   }
139   ```
140
141   ![en-us_image_0000001204538065](figures/en-us_image_0000001204538065.png)
142
1437. Create the **FoodGrid** component to function as a child component of the **TabContent** component.
144
145   ```ts
146   @Component
147   struct FoodGrid {
148     private foodItems: FoodData[]
149     build() {}
150   }
151
152   @Component
153   struct FoodCategory {
154     private foodItems: FoodData[]
155     build() {
156       Stack() {
157         Tabs() {
158           TabContent() {
159             FoodGrid({ foodItems: this.foodItems })
160           }.tabBar('All')
161         }
162         .barWidth(280)
163         .barMode(BarMode.Scrollable)
164       }
165     }
166   }
167   ```
168
1698. 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.
170
171   ```ts
172   @Component
173   struct FoodGrid {
174     private foodItems: FoodData[]
175     build() {
176       Scroll() {
177         Grid() {
178           ForEach(this.foodItems, (item: FoodData) => {
179             GridItem() {}
180           }, (item: FoodData) => item.id.toString())
181         }
182         .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
183         .columnsTemplate('1fr 1fr')
184         .columnsGap(8)
185         .rowsGap(8)
186       }
187       .scrollBar(BarState.Off)
188       .padding({left: 16, right: 16})
189     }
190   }
191   ```
192
1939. 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.
194
195   ```ts
196   @Component
197   struct FoodGridItem {
198     private foodItem: FoodData
199     build() {
200       Column() {
201         Row() {
202           Image(this.foodItem.image)
203             .objectFit(ImageFit.Contain)
204             .height(152)
205             .width('100%')
206         }
207         Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
208           Text(this.foodItem.name)
209             .fontSize(14)
210             .flexGrow(1)
211             .padding({ left: 8 })
212           Text(this.foodItem.calories + 'kcal')
213             .fontSize(14)
214             .margin({ right: 6 })
215         }
216         .height(32)
217         .width('100%')
218         .backgroundColor('#FFe5e5e5')
219       }
220       .height(184)
221       .width('100%')
222     }
223   }
224
225   @Component
226   struct FoodGrid {
227     private foodItems: FoodData[]
228     build() {
229       Scroll() {
230         Grid() {
231           ForEach(this.foodItems, (item: FoodData) => {
232             GridItem() {
233               FoodGridItem({foodItem: item})
234             }
235           }, (item: FoodData) => item.id.toString())
236         }
237         .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
238         .columnsTemplate('1fr 1fr')
239         .columnsGap(8)
240         .rowsGap(8)
241         .height(1144)
242       }
243       .scrollBar(BarState.Off)
244       .padding({ left: 16, right: 16 })
245     }
246   }
247   ```
248
249   ![en-us_image_0000001170167520](figures/en-us_image_0000001170167520.png)
250
25110. Create the **Category.Vegetable**, **Category.Fruit**, **Category.Nut**, **Category.SeaFood**, and **Category.Dessert** tabs.
252
253    ```ts
254    @Component
255    struct FoodCategory {
256      private foodItems: FoodData[]
257
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    ```ts
296    @Component
297    struct FoodGrid {
298      private foodItems: FoodData[]
299      private gridRowTemplate: string = ''
300      private heightValue: number
301
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       ```ts
326    aboutToAppear() {
327      var rows = Math.round(this.foodItems.length / 2);
328      this.gridRowTemplate = '1fr '.repeat(rows);
329      this.heightValue = rows * 192 - 8;
330    }
331       ```
332
333    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 before the custom component is destroyed.
334
335    ![en-us_image_0000001215113569](figures/en-us_image_0000001215113569.png)
336
337       ```ts
338    @Component
339    struct FoodGrid {
340      private foodItems: FoodData[]
341      private gridRowTemplate: string = ''
342      private heightValue: number
343
344      aboutToAppear() {
345        var rows = Math.round(this.foodItems.length / 2);
346        this.gridRowTemplate = '1fr '.repeat(rows);
347        this.heightValue = rows * 192 - 8;
348      }
349
350      build() {
351        Scroll() {
352          Grid() {
353            ForEach(this.foodItems, (item: FoodData) => {
354              GridItem() {
355                FoodGridItem({ foodItem: item })
356              }
357            }, (item: FoodData) => item.id.toString())
358          }
359          .rowsTemplate(this.gridRowTemplate)
360          .columnsTemplate('1fr 1fr')
361          .columnsGap(8)
362          .rowsGap(8)
363          .height(this.heightValue)
364        }
365        .scrollBar(BarState.Off)
366        .padding({ left: 16, right: 16 })
367      }
368    }
369       ```
370
371       ![en-us_image_0000001170008198](figures/en-us_image_0000001170008198.gif)
372