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