在这里插入图片描述

网罗开发 (小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


前言

ArkTS 声明式 UI 除了使用系统内置的 Text、Button、Column、Row 等,还需要把可复用的 UI 拆成自定义组件,并用合理的布局和 @Builder 减少重复代码。自定义组件和布局用得好,页面结构清晰、维护成本低;用得不好,容易变成「一大坨 build()」或属性传参混乱。

本文只讲自定义组件、@Builder、以及常用布局的关键写法和注意点,不贴完整页面 Demo。

自定义组件的基本写法

自定义组件就是一个用 @Component 装饰的 struct,内部有 build() 返回一棵 UI 树,外部通过构造函数传参。

无参组件

@Component
struct CardTitle {
  build() {
    Row() {
      Text('标题')
        .fontSize(18)
        .fontColor('#333333')
      Blank()
      Text('更多')
        .fontSize(14)
        .fontColor('#999999')
    }
    .width('100%')
    .padding(12)
  }
}

使用时直接当子节点写:

Column() {
  CardTitle()
  // 其他内容
}

带参组件:@Prop 与默认值

通过构造函数传参,用 @Prop 接收(只读),可带默认值:

@Component
struct ItemCard {
  @Prop title: string = '默认标题'
  @Prop desc: string = ''

  build() {
    Column() {
      Text(this.title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
      if (this.desc) {
        Text(this.desc)
          .fontSize(14)
          .fontColor('#666666')
          .margin({ top: 4 })
      }
    }
    .alignItems(HorizontalAlign.Start)
    .width('100%')
    .padding(12)
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
  }
}

使用:

ItemCard({ title: '卡片一', desc: '描述文字' })
ItemCard({ title: '仅标题' })  // desc 用默认值 ''

若需要「父改子、子改父」双向同步,用 @Link 配合父组件的 $xxx 传引用。

@Builder 复用 UI 片段

同一段 UI 在多个地方出现时,不必再拆一个组件,可用 @Builder 定义「UI 片段」,在 build 里多次调用。

无参 Builder

@Entry
@Component
struct Page {
  @Builder
  sectionTitle(title: string) {
    Text(title)
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 12 })
  }

  build() {
    Column() {
      this.sectionTitle('第一部分')
      // 内容...
      this.sectionTitle('第二部分')
      // 内容...
    }
  }
}

sectionTitle 只在当前组件内使用,参数直接写在方法参数里。

带尾随闭包的 Builder(@BuilderParam)

若希望「调用方传入一块自定义 UI」,可用 @BuilderParam 定义占位,调用时用尾随闭包传入:

@Component
struct ContainerCard {
  @BuilderParam header: () => void = () => {}
  @BuilderParam content: () => void = () => {}

  build() {
    Column() {
      this.header()
      this.content()
    }
    .width('100%')
    .backgroundColor('#FFF')
    .borderRadius(8)
    .padding(12)
  }
}

// 使用
ContainerCard() {
  Column() {
    Text('自定义头部')
  }
  .width('100%')
  .height(40)
  Column() {
    Text('自定义内容')
  }
}

注意:ArkTS 中 @BuilderParam 的写法与调用方式以当前 API 为准,有时需要配合 @Builder 或特定语法传递「一块 build 函数」。

常用布局与属性

Column / Row 的对齐与间距

  • Column.alignItems(HorizontalAlign.Start/Center/End) 控制子项水平对齐;.justifyContent(FlexAlign.Start/Center/SpaceBetween) 控制垂直方向分布
  • Row.alignItems(VerticalAlign.Top/Center/Bottom).justifyContent(FlexAlign.SpaceBetween) 同理,方向与 Column 对调

子项之间加间距可用 .margin({ top: 8 }) 或外层用 .gap(8)(若 API 支持)。

Flex 弹性布局

需要「按比例占宽」或「换行」时可用 Flex:

Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
  ForEach(this.tags, (item: string) => {
    Text(item)
      .fontSize(12)
      .padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .backgroundColor('#EEEEEE')
      .borderRadius(4)
  })
}
.width('100%')
.justifyContent(FlexAlign.Start)

FlexWrap.Wrap 表示子项排不下时换行,适合标签列表。

Stack 堆叠

用于「底层 + 上层叠加」(如图片 + 蒙层、图标 + 角标):

Stack() {
  Image($r('app.media.bg'))
    .width('100%')
    .height(120)
  Column() {
    Text('角标')
      .fontSize(10)
      .backgroundColor('#FF0000')
      .padding(2)
  }
  .position({ x: '80%', y: 8 })
}
.width('100%')
.height(120)

子节点顺序即叠放顺序,后写的在上层;.position() 用于绝对定位。

占满剩余空间

Column 中若希望某一块「把剩余高度占满」,可给该子节点 .layoutWeight(1)(在 Flex 或支持 layoutWeight 的容器中)。例如:

Column() {
  Text('顶部固定')
  List() { ... }
    .layoutWeight(1)   // 占满剩余空间
    .scrollBar(BarState.Off)
}
.height('100%')

这样列表会占据除顶部外的全部空间,并正常滚动。

尺寸与约束

  • 固定宽高.width(100).height(50).width('100%').height('100%')
  • 最大/最小.constraintSize({ minWidth: 100, maxWidth: 300 }) 等(以当前 API 为准)
  • 宽高比:部分组件支持 .aspectRatio(16/9),用于图片、视频等

避免在根 Column/Row 上不设宽高却期望「铺满」,否则可能布局异常;至少保证根节点有明确尺寸或 layoutWeight

总结

  • 自定义组件@Component + build(),通过构造函数和 @Prop/@Link 传参,便于复用和拆页。
  • @Builder:同一组件内复用 UI 片段;@BuilderParam 用于「由调用方传入一块 UI」的容器型组件。
  • 布局:Column/Row 负责线性排布与对齐;Flex 负责弹性、换行;Stack 负责堆叠;合理使用 layoutWeight 分配剩余空间。

按「页面 → 区块 → 小组件」的层级拆分,再配合 Builder 抽公共样式,就能在保持结构清晰的前提下减少重复代码。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐