当前位置:   article > 正文

【HarmonyOS】ArkUI - 自定义组件

【HarmonyOS】ArkUI - 自定义组件

【HarmonyOS】ArkUI - 列表布局(List) 那一篇文章的2024春节档电影新片票房榜列表例子为例:

@Entry
@Component
struct Index {
  ...

  build() {
    Column({ space: 8 }) {
      // 标题部分
      Row() {
        ...
      }

      // 电影列表部分
      List() {
        ForEach(
            ...
        )
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这是整体的代码结构,省略了细节部分。整个页面是一个从上到下的列式布局,所以我们使用了 Column 容器。然后页面分成了两部分,第一部分是顶部的标题,第二部分是电影列表,由于每一行的内容基本相似,所以我们使用 ForEach 在内部循环渲染电影对应的卡片。

一、创建自定义组件

以上面的标题部分为例,我们知道标题部分其实是一个标准化的功能,也就是说不仅这个页面需要这样的标题,其他页面也需要。比如产品在设计UE时,通常来说会把列表页面和列表详情页的标题部分设置成相似的。如果在每个页面里都写类似的标题代码,复用性会很差,所以为了解决这个问题我们可以把标题部分封装到自定义组件里。

@Component
struct Header {
  build() {
    // 标题部分
    Row() {
      ...
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们定义了一个结构体 Header,代表页面的头部。然后加上 @Component 装饰器,这样一个组件就声明出来了。紧接着,可以把标题部分的代码抽取到 builde() { } 里,这样一个可复用的标题的功能就封装好了。

将来在电影列表页面里,我需要写标题,不需要在重新写标题代码了,直接引用这个 Header 组件就行了:

@Entry
@Component
struct Index {
  ...

  build() {
    Column({ space: 8 }) {
      // 标题部分
      Header()

      // 电影列表部分
      List() {
        ForEach(
            ...
        )
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

定义在页面内部的自定义组件完整代码如下:

@Component
struct Header {
  private title: string

  build() {
    Row() {
      Text(this.title)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
    }
  }
}

@Entry
@Component
struct Index {
  ...

  build() {
    Column({ space: 8 }) {
      // 标题部分
      Header({ title: '2024春节档新片票房榜' })
        .margin({ bottom: 20 })

      ...
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

但是,如果定义在页面内部,也就意味着只有在这个页面能用,换一个页面就用不了了。所以最佳的方案时定义在单独的文件里。

在 entry 的 ets 文件夹里新建 components 文件夹,并在里面新建一个 CommonComponents.ets 文件。将上面 Header 代码拷贝到这个文件里,为了能让别的文件使用这段代码,需要做一些修改:

@Component
export struct Header {
  build() {
    Row() {
      ...
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用 export 将 Header 导出,这样别的文件才能 import 导入使用:

import { Header } from '../components/CommonComponents'
  • 1

定义在页面外部的自定义组件完整代码如下:

CommonComponents.ets

@Component
export struct Header {
  private title: string

  build() {
    Row() {
      Text(this.title)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Index.ets

import { Header } from '../components/CommonComponents'

@Entry
@Component
struct Index {
  ...

  build() {
    Column({ space: 8 }) {
      // 标题部分
      Header({ title: '2024春节档新片票房榜' })
        .margin({ bottom: 20 })

      ...
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

二、自定义构建函数 @Builder

这是我们的 Index.ets 代码:

...

@Entry
@Component
struct Index {
  ...

  build() {
    Column({ space: 8 }) {
      ...

      // 电影列表部分
      List({ space: 8 }) {
        ForEach(
          this.items,
          (item: Item) => {
            ListItem() {
              Row({ space: 8 }) {
                Image(item.image)
                  .width(157)
                  .height(220)
                Column() {
                  Text(item.name)
                    .fontSize(20)
                    .fontWeight(FontWeight.Bold)
                  Text(item.box_office)
                    .fontSize(18)
                }
                .height('100%')
                .alignItems(HorizontalAlign.Start)
              }
              .width('100%')
              .height(220)
            }
          }
        )
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .padding(8)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

从这段代码可以发现,List 里面的代码可读性不高。最好是能把下面这段代码封装起来:

Row({ space: 8 }) {
  Image(item.image)
    .width(157)
    .height(220)
  Column() {
    Text(item.name)
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    Text(item.box_office)
    .fontSize(18)
  }
  .height('100%')
  .alignItems(HorizontalAlign.Start)
}
.width('100%')
.height(220)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以使用自定义组件,也可以使用自定构建函数 @Builder,它用来做这种内部的页面封装会更加合适一些。

自定构建函数顾名思义就是用来构建页面的一个函数,可以把相关代码封装进去:

  1. 全局自定义构建函数

    代码结构:

    @Builder function 函数名() {
      ...
    }
    
    • 1
    • 2
    • 3

    完整代码:

    ...
    
    @Builder function ItemCard(item:Item) {
      Row({ space: 8 }) {
        Image(item.image)
          .width(157)
          .height(220)
        Column() {
          Text(item.name)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
          Text(item.box_office)
            .fontSize(18)
        }
        .height('100%')
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .height(220)
    }
    
    @Entry
    @Component
    struct Index {
      ...
    
      build() {
        Column({ space: 8 }) {
          ...
          
          // 电影列表部分
          List({ space: 8 }) {
            ForEach(
              this.items,
              (item: Item) => {
                ListItem() {
                  ItemCard(item)
                }
              }
            )
          }
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
        .padding(8)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
  2. 组件内自定义构建函数

    代码结构:

    @Builder 函数名() {
      ...
    }
    
    • 1
    • 2
    • 3

    完整代码:

    ...
    
    @Entry
    @Component
    struct Index {
      ...
    
      build() {
        Column({ space: 8 }) {
          ...
          
          // 电影列表部分
          List({ space: 8 }) {
            ForEach(
              this.items,
              (item: Item) => {
                ListItem() {
                  this.ItemCard(item)
                }
              }
            )
          }
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
        .padding(8)
      }
    
      @Builder ItemCard(item: Item) {
        Row({ space: 8 }) {
          Image(item.image)
            .width(157)
            .height(220)
          Column() {
            Text(item.name)
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
            Text(item.box_office)
              .fontSize(18)
          }
          .height('100%')
          .alignItems(HorizontalAlign.Start)
        }
        .width('100%')
        .height(220)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

三、自定义公共样式 @Styles

@Entry
@Component
struct Index {
  ...

  build() {
    Column() {
      ...
    }
    .width('100%')
    .height('100%')
    .padding(8)
  }

  ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如上代码所示,是一个 Column 容器,这个 Column 是有很多样式的,比如这里的宽100%、高100%等,这种样式可以认为是 App 的统一样式,也就是通用样式,如果每个页面都去写这些代码是不是也是浪费,这种也可以做抽取,这是对样式的抽取,就要用到 @Styles 装饰器。

  1. 全局公共样式

    代码结构:

    @Styles function 函数名() {
      ...
    }
    
    • 1
    • 2
    • 3

    完整代码:

    @Styles function fillScreen() {
      .width('100%')
      .height('100%')
      .padding(8)
    }
    
    @Entry
    @Component
    struct Index {
      ...
    
      build() {
        Column() {
          ...
        }
        .fillScreen()
      }
    
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  2. 内部共样式

    代码结构:

    @Styles 函数名() {
      ...
    }
    
    • 1
    • 2
    • 3

    完整代码:

    @Entry
    @Component
    struct Index {
      @Styles function fillScreen() {
        .width('100%')
        .height('100%')
        .padding(8)
      }
      
      ...
    
      build() {
        Column() {
          ...
        }
        .fillScreen()
      }
    
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

四、自定义组件特有属性 @Extend

Text(item.name)
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
  • 1
  • 2
  • 3

如上代码所示,fontSize 和 fontWeight 是 Text 组件的特有属性,如果页面中有相同的代码,可以使用 @Extend 抽取。

代码结构:

@Extend(组件) function 函数名() {
  ...
}
  • 1
  • 2
  • 3

完整代码:

@Extend(Text) function nameText() {
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
}

Text(item.name)
  .nameText()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

切记 @Extend 不能写在组件内,只能写在全局。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/240003
推荐阅读
相关标签
  

闽ICP备14008679号