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  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  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  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  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  372