Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArkUI - 自定义组件 #15

Open
cnwutianhao opened this issue Feb 21, 2024 · 0 comments
Open

ArkUI - 自定义组件 #15

cnwutianhao opened this issue Feb 21, 2024 · 0 comments

Comments

@cnwutianhao
Copy link
Owner

cnwutianhao commented Feb 21, 2024

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

@Entry
@Component
struct Index {
  ...

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

      // 电影列表部分
      List() {
        ForEach(
            ...
        )
      }
    }
  }
}

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

一、创建自定义组件

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

@Component
struct Header {
  build() {
    // 标题部分
    Row() {
      ...
    }
  }
}

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

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

@Entry
@Component
struct Index {
  ...

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

      // 电影列表部分
      List() {
        ForEach(
            ...
        )
      }
    }
  }
}

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

@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 })

      ...
    }
  }
}

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

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

@Component
export struct Header {
  build() {
    Row() {
      ...
    }
  }
}

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

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

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

CommonComponents.ets

@Component
export struct Header {
  private title: string

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

Index.ets

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

@Entry
@Component
struct Index {
  ...

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

      ...
    }
  }
}

二、自定义构建函数 @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)
  }
}

从这段代码可以发现,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)

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

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

  1. 全局自定义构建函数

    代码结构:

    @Builder function 函数名() {
      ...
    }
    

    完整代码:

    ...
    
    @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)
      }
    }
    
  2. 组件内自定义构建函数

    代码结构:

    @Builder 函数名() {
      ...
    }
    

    完整代码:

    ...
    
    @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)
      }
    }
    

三、自定义公共样式 @Styles

@Entry
@Component
struct Index {
  ...

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

  ...
}

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

  1. 全局公共样式

    代码结构:

    @Styles function 函数名() {
      ...
    }

    完整代码:

    @Styles function fillScreen() {
      .width('100%')
      .height('100%')
      .padding(8)
    }
    
    @Entry
    @Component
    struct Index {
      ...
    
      build() {
        Column() {
          ...
        }
        .fillScreen()
      }
    
      ...
    }
  2. 内部共样式

    代码结构:

    @Styles 函数名() {
      ...
    }

    完整代码:

    @Entry
    @Component
    struct Index {
      @Styles function fillScreen() {
        .width('100%')
        .height('100%')
        .padding(8)
      }
      
      ...
    
      build() {
        Column() {
          ...
        }
        .fillScreen()
      }
    
      ...
    }

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

Text(item.name)
  .fontSize(20)
  .fontWeight(FontWeight.Bold)

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

代码结构:

@Extend(组件) function 函数名() {
  ...
}

完整代码:

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

Text(item.name)
  .nameText()

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant