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 12 在index.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  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  50 513. 通过资源访问图片。 52 除指定图片路径外,也可以使用引用媒体资源符$r引用资源,需要遵循resources文件夹的资源限定词的规则。右键resources文件夹,点击New>Resource Directory,选择Resource Type为Media(图片资源)。 53 54 将Tomato.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  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  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  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  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  312 3134. 仿照热量分类创建营养成分分类。营养成分部分(Nutrition)包含:蛋白质(Protein)、脂肪(Fat)、碳水化合物(Carbohydrates)和维生素C(VitaminC)四个成分,后三个成分在表格中省略分类名,用空格代替。 314 设置外层Flex为竖直排列FlexDirection.Column, 在主轴方向(竖直方向)上等距排列FlexAlign.SpaceBetween,在交叉轴方向(水平轴方向)上首部对齐排列ItemAlign.Start。 315 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  411 4125. 使用自定义构造函数\@Builder简化代码。可以发现,每个成分表中的成分单元其实都是一样的UI结构。 413  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  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