• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 构建食物分类Grid布局
2
3健康饮食应用在主页提供给用户两种食物显示方式:列表显示和网格显示。开发者将实现通过页签切换不同食物分类的网格布局。
4
5
61. 将Category枚举类型引入FoodCategoryList页面。
7
8   ```ts
9   import { Category, FoodData } from '../model/FoodData'
10   ```
11
122. 创建FoodCategoryList和FoodCategory组件,其中FoodCategoryList作为新的页面入口组件,在入口组件调用initializeOnStartup方法。
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. 在FoodCategoryList组件内创建showList成员变量,用于控制List布局和Grid布局的渲染切换。需要用到条件渲染语句if...else...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. 在页面右上角创建切换List/Grid布局的图标。设置Stack对齐方式为顶部尾部对齐TopEnd,创建Image组件,设置其点击事件,即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. 添加@State装饰器。点击右上角的switch标签后,页面没有任何变化,这是因为showList不是有状态数据,它的改变不会触发页面的刷新。需要为其添加\@State装饰器,使其成为状态数据,它的改变会引起其所在组件的重新渲染。
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   点击切换图标,FoodList组件出现,再次点击,FoodList组件消失。
120
121   ![zh-cn_image_0000001170411978](figures/zh-cn_image_0000001170411978.gif)
122
1236. 创建显示所有食物的页签(All)。在FoodCategory组件内创建Tabs组件和其子组件TabContent,设置tabBar为All。设置TabBars的宽度为280,布局模式为Scrollable,即超过总长度后可以滑动。Tabs是一种可以通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图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   ![zh-cn_image_0000001204538065](figures/zh-cn_image_0000001204538065.png)
142
1437. 创建FoodGrid组件,作为TabContent的子组件。
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. 实现2 \* 6的网格布局(一共12个食物数据资源)。创建Grid组件,设置列数columnsTemplate('1fr 1fr'),行数rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr'),行间距和列间距rowsGap和columnsGap为8。创建Scroll组件,使其可以滑动。
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. 创建FoodGridItem组件,展示食物图片、名称和卡路里,实现其UI布局,为GridItem的子组件。每个FoodGridItem高度为184,行间距为8,设置Grid总高度为(184 + 8) \* 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   ![zh-cn_image_0000001170167520](figures/zh-cn_image_0000001170167520.png)
250
25110. 创建展示蔬菜(Category.Vegetable)、水果(Category.Fruit)、坚果(Category.Nut)、海鲜(Category.SeaFood)和甜品(Category.Dessert)分类的页签。
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. 设置不同食物分类的Grid的行数和高度。因为不同分类的食物数量不同,所以不能用'1fr 1fr 1fr 1fr 1fr 1fr '常量来统一设置成6行。
293      创建gridRowTemplate和HeightValue成员变量,通过成员变量设置Grid行数和高度。
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    调用aboutToAppear接口计算行数(gridRowTemplate )和高度(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    自定义组件提供了两个生命周期的回调接口aboutToAppear和aboutToDisappear。aboutToAppear的执行时机在创建自定义组件后,执行自定义组件build方法之前。aboutToDisappear在自定义组件销毁之前的时机执行。
334
335    ![zh-cn_image_0000001215113569](figures/zh-cn_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       ![zh-cn_image_0000001170008198](figures/zh-cn_image_0000001170008198.gif)