• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 创建简单视图
2
3在这一小节中,我们将开始食物详情页的开发,学习如何通过容器组件Stack、Flex和基础组件Image、Text,构建用户自定义组件,完成图文并茂的食物介绍。
4
5在创建页面前,请先创建ArkTS工程,Stage模型请参考[创建Stage模型的ArkTS工程](../quick-start/start-with-ets-stage.md#创建arkts工程),FA模型请参考[创建FA模型的ArkTS工程](../quick-start/start-with-ets-fa.md#创建arkts工程)。
6
7
8## 构建Stack布局
9
101. 创建食物名称。
11
12index.ets文件中,创建Stack组件,将Text组件放进Stack组件的花括号中,使其成为Stack组件的子组件。Stack组件为堆叠组件,可以包含一个或多个子组件,其特点是后一个子组件覆盖前一个子组件。
13
14   ```ts
15   @Entry
16   @Component
17   struct MyComponent {
18     build() {
19       Stack() {
20           Text('Tomato')
21               .fontSize(26)
22               .fontWeight(500)
23       }
24     }
25   }
26   ```
27
28   ![zh-cn_image_0000001214128687](figures/zh-cn_image_0000001214128687.png)
29
302. 食物图片展示。
31   创建Image组件,指定Image组件的url,Image组件是必选构造参数组件。为了让Text组件在Image组件上方显示,所以要先声明Image组件。图片资源放在resources下的rawfile文件夹内,引用rawfile下资源时使用`$rawfile('filename')`的形式,filename为rawfile目录下的文件相对路径。当前`$rawfile`仅支持Image控件引用图片资源。
32
33   ```ts
34   @Entry
35   @Component
36   struct MyComponent {
37     build() {
38       Stack() {
39           Image($rawfile('Tomato.png'))
40           Text('Tomato')
41               .fontSize(26)
42               .fontWeight(500)
43       }
44     }
45   }
46   ```
47
48
49   ![zh-cn_image_0000001168410342](figures/zh-cn_image_0000001168410342.png)
50
513. 通过资源访问图片。
52   除指定图片路径外,也可以使用引用媒体资源符$r引用资源,需要遵循resources文件夹的资源限定词的规则。右键resources文件夹,点击New>Resource Directory,选择Resource Type为Media(图片资源)。
53
54Tomato.png放入media文件夹内。就可以通过`$r('app.type.name')`的形式引用应用资源,即`$r('app.media.Tomato')`。
55
56   ```ts
57   @Entry
58   @Component
59   struct MyComponent {
60     build() {
61       Stack() {
62         Image($r('app.media.Tomato'))
63           .objectFit(ImageFit.Contain)
64           .height(357)
65         Text('Tomato')
66           .fontSize(26)
67           .fontWeight(500)
68       }
69     }
70   }
71   ```
72
734. 设置Image宽高,并且将image的objectFit属性设置为ImageFit.Contain,即保持图片长宽比的情况下,使得图片完整地显示在边界内。
74   如果Image填满了整个屏幕,原因如下:
75   1. Image没有设置宽高。
76
77   2. Image的objectFit默认属性是ImageFit.Cover,即在保持长宽比的情况下放大或缩小,使其填满整个显示边界。
78
79   ```ts
80   @Entry
81   @Component
82   struct MyComponent {
83     build() {
84       Stack() {
85         Image($r('app.media.Tomato'))
86           .objectFit(ImageFit.Contain)
87           .height(357)
88         Text('Tomato')
89           .fontSize(26)
90           .fontWeight(500)
91       }
92     }
93   }
94   ```
95
96     ![zh-cn_image_0000001214210217](figures/zh-cn_image_0000001214210217.png)
97
985. 设置食物图片和名称布局。设置Stack的对齐方式为底部起始端对齐,Stack默认为居中对齐。设置Stack构造参数alignContent为Alignment.BottomStart。其中Alignment和FontWeight一样,都是框架提供的内置枚举类型。
99
100   ```ts
101   @Entry
102   @Component
103   struct MyComponent {
104     build() {
105       Stack({ alignContent: Alignment.BottomStart }) {
106         Image($r('app.media.Tomato'))
107           .objectFit(ImageFit.Contain)
108           .height(357)
109         Text('Tomato')
110           .fontSize(26)
111           .fontWeight(500)
112       }
113     }
114   }
115   ```
116
117     ![zh-cn_image_0000001168728872](figures/zh-cn_image_0000001168728872.png)
118
1196. 调整Text组件的外边距margin,使其距离左侧和底部有一定的距离。margin是简写属性,可以统一指定四个边的外边距,也可以分别指定。具体设置方式如下:
120
121   1. 参数为Length时,即统一指定四个边的外边距,比如margin(20),即上、右、下、左四个边的外边距都是20。
122   2. 参数为{top?: Length, right?: Length, bottom?: Length, left?:Length},即分别指定四个边的边距,比如margin({ left: 26, bottom: 17.4 }),即左边距为26,下边距为17.4。
123
124   ```ts
125   @Entry
126   @Component
127   struct MyComponent {
128     build() {
129       Stack({ alignContent: Alignment.BottomStart }) {
130           Image($r('app.media.Tomato'))
131               .objectFit(ImageFit.Contain)
132               .height(357)
133           Text('Tomato')
134               .fontSize(26)
135               .fontWeight(500)
136               .margin({left: 26, bottom: 17.4})
137       }
138     }
139   }
140   ```
141
142     ![zh-cn_image_0000001213968747](figures/zh-cn_image_0000001213968747.png)
143
1447. 调整组件间的结构,语义化组件名称。创建页面入口组件为FoodDetail,在FoodDetail中创建Column,设置水平方向上居中对齐 alignItems(HorizontalAlign.Center)。MyComponent组件名改为FoodImageDisplay,为FoodDetail的子组件。
145
146   Column是子组件竖直排列的容器组件,本质为线性布局,所以只能设置交叉轴方向的对齐。
147
148   ```ts
149   @Component
150   struct FoodImageDisplay {
151     build() {
152       Stack({ alignContent: Alignment.BottomStart }) {
153         Image($r('app.media.Tomato'))
154           .objectFit(ImageFit.Contain)
155         Text('Tomato')
156           .fontSize(26)
157           .fontWeight(500)
158           .margin({ left: 26, bottom: 17.4 })
159       }
160       .height(357)
161     }
162   }
163
164   @Entry
165   @Component
166   struct FoodDetail {
167     build() {
168       Column() {
169         FoodImageDisplay()
170       }
171       .alignItems(HorizontalAlign.Center)
172     }
173   }
174   ```
175
176## 构建Flex布局
177
178开发者可以使用Flex弹性布局来构建食物的食物成分表,弹性布局在本场景的优势在于可以免去多余的宽高计算,通过比例来设置不同单元格的大小,更加灵活。
179
1801. 创建ContentTable组件,使其成为页面入口组件FoodDetail的子组件。
181
182   ```ts
183   @Component
184   struct FoodImageDisplay {
185     build() {
186       Stack({ alignContent: Alignment.BottomStart }) {
187         Image($r('app.media.Tomato'))
188           .objectFit(ImageFit.Contain)
189           .height(357)
190         Text('Tomato')
191           .fontSize(26)
192           .fontWeight(500)
193           .margin({ left: 26, bottom: 17.4 })
194       }
195     }
196   }
197
198   @Component
199   struct ContentTable {
200     build() {
201     }
202   }
203
204   @Entry
205   @Component
206   struct FoodDetail {
207     build() {
208       Column() {
209         FoodImageDisplay()
210         ContentTable()
211       }
212       .alignItems(HorizontalAlign.Center)
213     }
214   }
215   ```
216
2172. 创建Flex组件展示Tomato两类成分。
218   一类是热量Calories,包含卡路里(Calories);一类是营养成分Nutrition,包含蛋白质(Protein)、脂肪(Fat)、碳水化合物(Carbohydrates)和维生素C(VitaminC)。
219
220   先创建热量这一类。创建Flex组件,高度为280,上、右、左内边距为30,包含三个Text子组件分别代表类别名(Calories),含量名称(Calories)和含量数值(17kcal)。Flex组件默认为水平排列方式。
221
222   已省略FoodImageDisplay代码,只针对ContentTable进行扩展。
223
224   ```ts
225   @Component
226   struct ContentTable {
227     build() {
228       Flex() {
229         Text('Calories')
230           .fontSize(17.4)
231           .fontWeight(FontWeight.Bold)
232         Text('Calories')
233           .fontSize(17.4)
234         Text('17kcal')
235           .fontSize(17.4)
236       }
237       .height(280)
238       .padding({ top: 30, right: 30, left: 30 })
239     }
240   }
241
242   @Entry
243   @Component
244   struct FoodDetail {
245     build() {
246       Column() {
247         FoodImageDisplay()
248         ContentTable()
249       }
250       .alignItems(HorizontalAlign.Center)
251     }
252   }
253   ```
254
255
256      ![zh-cn_image_0000001169759552](figures/zh-cn_image_0000001169759552.png)
257
2583. 调整布局,设置各部分占比。分类名占比(layoutWeight)为1,成分名和成分含量一共占比(layoutWeight)2。成分名和成分含量位于同一个Flex中,成分名占据所有剩余空间flexGrow(1)。
259
260   ```ts
261   @Component
262   struct FoodImageDisplay {
263     build() {
264       Stack({ alignContent: Alignment.BottomStart }) {
265         Image($r('app.media.Tomato'))
266           .objectFit(ImageFit.Contain)
267           .height(357)
268         Text('Tomato')
269           .fontSize(26)
270           .fontWeight(500)
271           .margin({ left: 26, bottom: 17.4 })
272       }
273     }
274   }
275
276   @Component
277   struct ContentTable {
278     build() {
279       Flex() {
280         Text('Calories')
281           .fontSize(17.4)
282           .fontWeight(FontWeight.Bold)
283           .layoutWeight(1)
284         Flex() {
285           Text('Calories')
286             .fontSize(17.4)
287             .flexGrow(1)
288           Text('17kcal')
289             .fontSize(17.4)
290         }
291         .layoutWeight(2)
292       }
293       .height(280)
294       .padding({ top: 30, right: 30, left: 30 })
295     }
296   }
297
298   @Entry
299   @Component
300   struct FoodDetail {
301     build() {
302       Column() {
303         FoodImageDisplay()
304         ContentTable()
305       }
306       .alignItems(HorizontalAlign.Center)
307     }
308   }
309   ```
310
311     ![zh-cn_image_0000001215079443](figures/zh-cn_image_0000001215079443.png)
312
3134. 仿照热量分类创建营养成分分类。营养成分部分(Nutrition)包含:蛋白质(Protein)、脂肪(Fat)、碳水化合物(Carbohydrates)和维生素C(VitaminC)四个成分,后三个成分在表格中省略分类名,用空格代替。
314   设置外层Flex为竖直排列FlexDirection.Column, 在主轴方向(竖直方向)上等距排列FlexAlign.SpaceBetween,在交叉轴方向(水平轴方向)上首部对齐排列ItemAlign.Start315
316   ```ts
317   @Component
318   struct ContentTable {
319     build() {
320       Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
321         Flex() {
322           Text('Calories')
323             .fontSize(17.4)
324             .fontWeight(FontWeight.Bold)
325             .layoutWeight(1)
326           Flex() {
327             Text('Calories')
328               .fontSize(17.4)
329               .flexGrow(1)
330             Text('17kcal')
331               .fontSize(17.4)
332           }
333           .layoutWeight(2)
334         }
335         Flex() {
336           Text('Nutrition')
337             .fontSize(17.4)
338             .fontWeight(FontWeight.Bold)
339             .layoutWeight(1)
340           Flex() {
341             Text('Protein')
342               .fontSize(17.4)
343               .flexGrow(1)
344             Text('0.9g')
345               .fontSize(17.4)
346           }
347           .layoutWeight(2)
348         }
349         Flex() {
350           Text(' ')
351             .fontSize(17.4)
352             .fontWeight(FontWeight.Bold)
353             .layoutWeight(1)
354           Flex() {
355             Text('Fat')
356               .fontSize(17.4)
357               .flexGrow(1)
358             Text('0.2g')
359               .fontSize(17.4)
360           }
361           .layoutWeight(2)
362         }
363         Flex() {
364           Text(' ')
365             .fontSize(17.4)
366             .fontWeight(FontWeight.Bold)
367             .layoutWeight(1)
368           Flex() {
369             Text('Carbohydrates')
370               .fontSize(17.4)
371               .flexGrow(1)
372             Text('3.9g')
373               .fontSize(17.4)
374           }
375           .layoutWeight(2)
376         }
377         Flex() {
378           Text(' ')
379             .fontSize(17.4)
380             .fontWeight(FontWeight.Bold)
381             .layoutWeight(1)
382           Flex() {
383             Text('vitaminC')
384               .fontSize(17.4)
385               .flexGrow(1)
386             Text('17.8mg')
387               .fontSize(17.4)
388           }
389           .layoutWeight(2)
390         }
391       }
392       .height(280)
393       .padding({ top: 30, right: 30, left: 30 })
394     }
395   }
396
397   @Entry
398   @Component
399   struct FoodDetail {
400       build() {
401           Column() {
402               FoodImageDisplay()
403               ContentTable()
404           }
405           .alignItems(HorizontalAlign.Center)
406       }
407   }
408   ```
409
410   ![zh-cn_image_0000001215199399](figures/zh-cn_image_0000001215199399.png)
411
4125. 使用自定义构造函数\@Builder简化代码。可以发现,每个成分表中的成分单元其实都是一样的UI结构。
413   ![zh-cn_image_0000001169599582](figures/zh-cn_image_0000001169599582.png)
414
415   当前对每个成分单元都进行了声明,造成了代码的重复和冗余。可以使用\@Builder来构建自定义方法,抽象出相同的UI结构声明。\@Builder修饰的方法和Component的build方法都是为了声明一些UI渲染结构,遵循一样的ArkTS语法。开发者可以定义一个或者多个\@Builder修饰的方法,但Component的build方法必须只有一个。
416
417   在ContentTable内声明\@Builder修饰的IngredientItem方法,用于声明分类名、成分名称和成分含量UI描述。
418
419   ```ts
420    @Component
421   struct ContentTable {
422     @Builder IngredientItem(title:string, name: string, value: string) {
423       Flex() {
424         Text(title)
425           .fontSize(17.4)
426           .fontWeight(FontWeight.Bold)
427           .layoutWeight(1)
428         Flex({ alignItems: ItemAlign.Center }) {
429           Text(name)
430             .fontSize(17.4)
431             .flexGrow(1)
432           Text(value)
433             .fontSize(17.4)
434         }
435         .layoutWeight(2)
436       }
437     }
438   }
439   ```
440
441   在ContentTable的build方法内调用IngredientItem接口,需要用this去调用该Component作用域内的方法,以此来区分全局的方法调用。
442
443   ```ts
444   @Component
445   struct ContentTable {
446     ......
447     build() {
448       Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
449         this.IngredientItem('Calories', 'Calories', '17kcal')
450         this.IngredientItem('Nutrition', 'Protein', '0.9g')
451         this.IngredientItem('', 'Fat', '0.2g')
452         this.IngredientItem('', 'Carbohydrates', '3.9g')
453         this.IngredientItem('', 'VitaminC', '17.8mg')
454       }
455       .height(280)
456       .padding({ top: 30, right: 30, left: 30 })
457     }
458   }
459   ```
460
461   ContentTable组件整体代码如下。
462
463   ```ts
464   @Component
465   struct ContentTable {
466     @Builder IngredientItem(title:string, name: string, value: string) {
467       Flex() {
468         Text(title)
469           .fontSize(17.4)
470           .fontWeight(FontWeight.Bold)
471           .layoutWeight(1)
472         Flex() {
473           Text(name)
474             .fontSize(17.4)
475             .flexGrow(1)
476           Text(value)
477             .fontSize(17.4)
478         }
479         .layoutWeight(2)
480       }
481     }
482
483     build() {
484       Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
485         this.IngredientItem('Calories', 'Calories', '17kcal')
486         this.IngredientItem('Nutrition', 'Protein', '0.9g')
487         this.IngredientItem('', 'Fat', '0.2g')
488         this.IngredientItem('', 'Carbohydrates', '3.9g')
489         this.IngredientItem('', 'VitaminC', '17.8mg')
490       }
491       .height(280)
492       .padding({ top: 30, right: 30, left: 30 })
493     }
494   }
495
496   @Entry
497   @Component
498   struct FoodDetail {
499       build() {
500           Column() {
501               FoodImageDisplay()
502               ContentTable()
503           }
504           .alignItems(HorizontalAlign.Center)
505       }
506   }
507   ```
508
509     ![zh-cn_image_0000001215199399](figures/zh-cn_image_0000001215199399.png)
510
511通过学习Stack布局和Flex布局已完成食物的图文展示和营养成分表,构建出第一个普通视图的食物详情页。在下一个章节中,将开发食物分类列表页,并完成食物分类列表页面和食物详情页面的跳转和数据传递,现在我们就进入下一个章节的学习吧。
512
513## 相关实例
514
515针对创建简单视图,有以下示例工程可供参考:
516
517- [`BuildCommonView`:创建简单视图(ArkTS)(API8)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/ETSUI/BuildCommonView)
518
519  本示例为构建了简单页面展示食物番茄的图片和营养信息,主要为了展示简单页面的Stack布局和Flex布局。
520