• 鸿蒙:使用Stack、ContentTable、Flex等组件和布局实现一个显示界面


    效果展示

    38d92b82951c466c9eea6d47f2e94dd3.png

    一.概述

    跟随官网继续HarmonyOS学习

    本篇博文实现一个食物详情页的开发Demo

    通过这个开发过程学习如何使用容器组件StackFlex和基本组件ImageText构建用户自定义组件,完成图文并茂的食物介绍

    二.构建Stack布局

    1.食物名称

    创建Stack组件,Text子组件

    Stack组件为堆叠组件,可以包含一个或多个子组件,其特点是后一个子组件覆盖前一个子组件。

    1. @Entry
    2. @Component
    3. struct MyComponent {
    4. build() {
    5. Stack() {
    6. Text('Tomato')
    7. .fontSize(26)
    8. .fontWeight(500)
    9. .fontColor(Color.White)
    10. }
    11. }
    12. }

    Previewer效果: 

    34e2f012a9084c7f92a12bcabc48097f.png

    2.食物图片

    创建Image组件,指定Image组件的url

    Text组件要在Image组件上方显示,所以先声明Image组件。

    图片资源放在resources下的rawfile文件夹内,引用rawfile下资源时使用$rawfile('filename')的形式,filename为 rawfile 目录下的文件相对路径。

    当前$rawfile仅支持Image控件引用图片资源

    1. @Entry
    2. @Component
    3. struct MyComponent {
    4. build() {
    5. Stack() {
    6. Image($rawfile('Tomato.png'))
    7. Text('Tomato')
    8. .fontSize(26)
    9. .fontWeight(500)
    10. }
    11. }
    12. }

    Previewer效果: 

    5feee84032c84f249fee04095d06f75c.png

     

    3.通过资源访问图片

    除指定图片路径外,也可以使用引用 媒体资源符$r 引用资源,需要遵循resources文件夹的资源限定词的规则。

    右键resources文件夹,点击New>Resource Directory,选择Resource TypeMedia(图片资源)

    注:新建的Resource Directory目录只能在base目录下,但是base目录默认是有media文件的

    直接把Tomato.png放入media文件夹内,就可以通过$r('app.type.name')的形式引用应用资源了

    Tomato.png即为 $r('app.media.Tomato')

    代码:

    1. @Entry
    2. @Component
    3. struct MyComponent {
    4. build() {
    5. Stack() {
    6. Image($r('app.media.Tomato'))
    7. .objectFit(ImageFit.Contain)
    8. .height(357)
    9. //Image($rawfile('Tomato.png'))
    10. Text('Tomato')
    11. .fontSize(26)
    12. .fontWeight(500)
    13. }
    14. }
    15. }

    Previewer: 

    e3297d18775a46dcae3c8fba449f2caa.png

    4.Image组件objectFit属性

    示例中imageobjectFit属性设置为ImageFit.Contain
    即保持图片长宽比的情况下,使图片完整地显示在边界内。

    ImageobjectFit默认属性是ImageFit.Cover
    即在保持长宽比的情况下放大或缩小,使其填满整个显示边界。

    如果要想Image填满了整个屏幕,原因如下:
        1.Image没有设置宽高。
        2.objectFit属性使用默认值ImageFit.Cover

    5.设置Stack布局属性

    Stack默认为居中对齐,本示例中修改为底部起始端对齐
    设置Stack构造参数alignContentAlignment.BottomStart

    AlignmentFontWeight一样,都是框架提供的内置枚举类型

    代码:

    1. @Entry
    2. @Component
    3. struct MyComponent {
    4. build() {
    5. Stack({ alignContent: Alignment.BottomStart }) {
    6. Image($r('app.media.Tomato'))
    7. .objectFit(ImageFit.Contain)
    8. .height(357)
    9. //Image($rawfile('Tomato.png'))
    10. Text('Tomato')
    11. .fontSize(26)
    12. .fontWeight(500)
    13. }
    14. }
    15. }

    Previewer:  

    fe28ca1159ef4d879955177cfe18a768.png

    6.调整Text组件的外边距margin

    margin属性调整组件外边距

    (1).margin(Length),即上、右、下、左四个边的外边距都是Length

    (2).margin { top?: Length,
                        right?: Length,
                        bottom?: Length,
                        left?:Length },即分别指定四个边的边距

    代码:

    1. @Entry
    2. @Component
    3. struct MyComponent {
    4. build() {
    5. Stack({ alignContent: Alignment.BottomStart }) {
    6. Image($r('app.media.Tomato'))
    7. .objectFit(ImageFit.Contain)
    8. .height(357)
    9. Text('Tomato')
    10. .fontSize(26)
    11. .fontWeight(500)
    12. .margin({left: 26, bottom: 17.4})
    13. }
    14. }
    15. }

    Previewer:

    8509562c2d0542e6933b8586e782ec8b.png

    6.调整组件间的结构,语义化组件名称

    创建页面入口组件为FoodDetail,在FoodDetail中创建Column
    设置水平方向上居中对齐 alignItems(HorizontalAlign.Center)

    MyComponent组件名改为FoodImageDisplay,为FoodDetail的子组件 

    Column是子组件竖直排列的容器组件,本质为线性布局,所以只能设置交叉轴方向的对齐

    代码:

    1. @Component
    2. struct FoodImageDisplay {
    3. build() {
    4. Stack({ alignContent: Alignment.BottomStart }) {
    5. Image($r('app.media.Tomato'))
    6. .objectFit(ImageFit.Contain)
    7. Text('Tomato')
    8. .fontSize(26)
    9. .fontWeight(500)
    10. .margin({ left: 26, bottom: 17.4 })
    11. }
    12. .height(357)
    13. }
    14. }
    15. @Entry
    16. @Component
    17. struct FoodDetail {
    18. build() {
    19. Column() {
    20. FoodImageDisplay()
    21. }
    22. .alignItems(HorizontalAlign.Center)
    23. }
    24. }

    Previewer:

    3c7653ab992b4f8a9d23fb5b12497f65.png

    三.构建Flex布局

    Flex:弹性布局

    使用Flex弹性布局来构建食物的食物成分表,

    弹性布局在本场景的优势在于可以免去多余的宽高计算,通过比例来设置不同单元格的大小,更加灵活。

    1.新建ContentTable组件

    新建ContentTable组件,使其成为页面入口组件FoodDetail的子组件。

    代码:

    1. @Component
    2. struct FoodImageDisplay {
    3. build() {
    4. Stack({ alignContent: Alignment.BottomStart }) {
    5. Image($r('app.media.Tomato'))
    6. .objectFit(ImageFit.Contain)
    7. .height(357)
    8. Text('Tomato')
    9. .fontSize(26)
    10. .fontWeight(500)
    11. .margin({ left: 26, bottom: 17.4 })
    12. }
    13. }
    14. }
    15. @Component
    16. struct ContentTable {
    17. build() {}
    18. }
    19. @Entry
    20. @Component
    21. struct FoodDetail {
    22. build() {
    23. Column() {
    24. FoodImageDisplay()
    25. ContentTable()
    26. }
    27. .alignItems(HorizontalAlign.Center)
    28. }
    29. }

    Previewer:

    ContentTable子组件是空的,还没填充内容,当前Previewer效果与上一节一样。

    2.创建Flex组件展示Tomato两类成分

    一类是热量Calories:卡路里(Calories);

    一类是营养成分Nutrition,包含:蛋白质(Protein)、
                                                          脂肪(Fat)、
                                                          碳水化合物(Carbohydrates)
                                                          维生素C(VitaminC)。

    先创建热量这一类
    新建Flex组件,高度为280,上、右、左内边距为30,
    包含三个Text子组件分别代表:类别名(Calories)
                                                      含量名称(Calories)
                                                      含量数值(17kcal)
    Flex组件默认为水平排列方式。

    ContentTable代码:

    1. @Component
    2. struct ContentTable {
    3. build() {
    4. Flex() {
    5. Text('Calories')
    6. .fontSize(17.4)
    7. .fontWeight(FontWeight.Bold)
    8. Text('Calories')
    9. .fontSize(17.4)
    10. Text('17kcal')
    11. .fontSize(17.4)
    12. }
    13. .height(280)
    14. .padding({ top: 30, right: 30, left: 30 })
    15. }
    16. }

    Previewer:

    501da9cb100245a6bae2d7d7d108a657.png

    3.调整布局,设置各部分占比

    分类名占比(layoutWeight)为1,

    成分名和成分含量一共占比(layoutWeight)2。

    成分名和成分含量位于同一个Flex中,成分名占据所有剩余空间flexGrow(1)。

    ContentTable代码:

    1. @Component
    2. struct ContentTable {
    3. build() {
    4. Flex() {
    5. Text('Calories')
    6. .fontSize(17.4)
    7. .fontWeight(FontWeight.Bold)
    8. .layoutWeight(1)
    9. Flex() {
    10. Text('Calories')
    11. .fontSize(17.4)
    12. .flexGrow(1)
    13. Text('17kcal')
    14. .fontSize(17.4)
    15. }
    16. .layoutWeight(2)
    17. }
    18. .height(280)
    19. .padding({ top: 30, right: 30, left: 30 })
    20. }
    21. }

    Previewer:

    9ca5951920fa48509f45d094cc408cbe.png

    4.仿照热量分类创建营养成分分类

    营养成分部分(Nutrition)包含:
                      蛋白质(Protein)、
                      脂肪(Fat)、
                      碳水化合物(Carbohydrates)
                      维生素C(VitaminC)

    设置外层Flex为竖直排列 FlexDirection.Column
    在主轴方向(竖直方向)上等距排列 FlexAlign.SpaceBetween
    在交叉轴方向(水平轴方向)上首部对齐排列 ItemAlign.Start

    ContentTable代码:

    1. @Component
    2. struct ContentTable {
    3. build() {
    4. Flex({ direction: FlexDirection.Column,
    5. justifyContent: FlexAlign.SpaceBetween,
    6. alignItems: ItemAlign.Start }) {
    7. Flex() {
    8. Text('Calories')
    9. .fontSize(17.4)
    10. .fontWeight(FontWeight.Bold)
    11. .layoutWeight(1)
    12. Flex() {
    13. Text('Calories')
    14. .fontSize(17.4)
    15. .flexGrow(1)
    16. Text('17kcal')
    17. .fontSize(17.4)
    18. }
    19. .layoutWeight(2)
    20. }
    21. Flex() {
    22. Text('Nutrition')
    23. .fontSize(17.4)
    24. .fontWeight(FontWeight.Bold)
    25. .layoutWeight(1)
    26. Flex() {
    27. Text('Protein')
    28. .fontSize(17.4)
    29. .flexGrow(1)
    30. Text('0.9g')
    31. .fontSize(17.4)
    32. }
    33. .layoutWeight(2)
    34. }
    35. Flex() {
    36. Text(' ')
    37. .fontSize(17.4)
    38. .fontWeight(FontWeight.Bold)
    39. .layoutWeight(1)
    40. Flex() {
    41. Text('Fat')
    42. .fontSize(17.4)
    43. .flexGrow(1)
    44. Text('0.2g')
    45. .fontSize(17.4)
    46. }
    47. .layoutWeight(2)
    48. }
    49. Flex() {
    50. Text(' ')
    51. .fontSize(17.4)
    52. .fontWeight(FontWeight.Bold)
    53. .layoutWeight(1)
    54. Flex() {
    55. Text('Carbohydrates')
    56. .fontSize(17.4)
    57. .flexGrow(1)
    58. Text('3.9g')
    59. .fontSize(17.4)
    60. }
    61. .layoutWeight(2)
    62. }
    63. Flex() {
    64. Text(' ')
    65. .fontSize(17.4)
    66. .fontWeight(FontWeight.Bold)
    67. .layoutWeight(1)
    68. Flex() {
    69. Text('vitaminC')
    70. .fontSize(17.4)
    71. .flexGrow(1)
    72. Text('17.8mg')
    73. .fontSize(17.4)
    74. }
    75. .layoutWeight(2)
    76. }
    77. }
    78. .height(280)
    79. .padding({ top: 30, right: 30, left: 30 })
    80. }
    81. }

    Previewer:

    38d92b82951c466c9eea6d47f2e94dd3.png

    5.优化代码

    可以发现,每个成分表中的成分单元其实都是一样的UI结构

    2c75982c6a0849be90ac1400fa46ee09.png

    可以通过自定义@Builder函数对代码进行精简

    使用自定义@Builder抽象出相同的UI结构
    @Builder修饰的方法和Componentbuild方法都是为了声明一些UI渲染结构,遵循一样的ArkTS语法。

    可以定义一个或者多个 @Builder修饰的方法,但Componentbuild方法必须只有一个

    ContentTable内声明@Builder修饰的IngredientItem方法,用于声明分类名、成分名称和成分含量UI描述。

    1. @Component
    2. struct ContentTable {
    3. @Builder IngredientItem(title:string, name: string, value: string) {
    4. Flex() {
    5. Text(title)
    6. .fontSize(17.4)
    7. .fontWeight(FontWeight.Bold)
    8. .layoutWeight(1)
    9. Flex({ alignItems: ItemAlign.Center }) {
    10. Text(name)
    11. .fontSize(17.4)
    12. .flexGrow(1)
    13. Text(value)
    14. .fontSize(17.4)
    15. }
    16. .layoutWeight(2)
    17. }
    18. }
    19. }

    ContentTablebuild方法内调用IngredientItem接口,
    需要用this去调用该Component作用域内的方法,以此来区分全局的方法调用。

    1. @Component
    2. struct ContentTable {
    3. ......
    4. build() {
    5. Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) {
    6. this.IngredientItem('Calories', 'Calories', '17kcal')
    7. this.IngredientItem('Nutrition', 'Protein', '0.9g')
    8. this.IngredientItem('', 'Fat', '0.2g')
    9. this.IngredientItem('', 'Carbohydrates', '3.9g')
    10. this.IngredientItem('', 'VitaminC', '17.8mg')
    11. }
    12. .height(280)
    13. .padding({ top: 30, right: 30, left: 30 })
    14. }
    15. }

    ContentTable组件全代码如下:

    1. @Component
    2. struct ContentTable {
    3. @Builder
    4. IngredientItem(title:string, name: string, value: string) {
    5. Flex() {
    6. Text(title)
    7. .fontSize(17.4)
    8. .fontWeight(FontWeight.Bold)
    9. .layoutWeight(1)
    10. Flex() {
    11. Text(name)
    12. .fontSize(17.4)
    13. .flexGrow(1)
    14. Text(value)
    15. .fontSize(17.4)
    16. }
    17. .layoutWeight(2)
    18. }
    19. }
    20. build() {
    21. Flex({ direction: FlexDirection.Column,
    22. justifyContent: FlexAlign.SpaceBetween,
    23. alignItems: ItemAlign.Start }) {
    24. this.IngredientItem('Calories', 'Calories', '17kcal')
    25. this.IngredientItem('Nutrition', 'Protein', '0.9g')
    26. this.IngredientItem('', 'Fat', '0.2g')
    27. this.IngredientItem('', 'Carbohydrates', '3.9g')
    28. this.IngredientItem('', 'VitaminC', '17.8mg')
    29. }
    30. .height(280)
    31. .padding({ top: 30, right: 30, left: 30 })
    32. }
    33. }

    Previewer:

    本小节只是优化代码,实现效果与上一小节相同

    四.结束语

    Stack布局Flex布局已完成食物的图文展示和营养成分表,构建出了第一个普通视图的食物详情页

    下一篇博文将继续跟随官网,开发食物分类列表页,并完成食物分类列表页面和食物详情页面的跳转和数据传递。

     

  • 相关阅读:
    Hive修改Parquet类型表字段几种问题处理
    上帝视角看Vue源码整体架构+相关源码问答
    文字以打字样式展现形式
    SAP 销售订单审批状态参数设置
    音频——硬件拓扑
    TorchV的RAG实践分享(三):解析llama_index的数据存储结构和召回策略过程
    前端笔试题总结,带答案和解析(一)
    Windows Server 2016安装SQLServer2008R2
    bash install.sh ********错误
    【数据分析】:什么是数据分析?
  • 原文地址:https://blog.csdn.net/geyichongchujianghu/article/details/134491958