HarmonyOS 自定义组件与布局实践


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

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括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 树,外部通过构造函数传参。

无参组件

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

使用时直接当子节点写:

typescript 复制代码
Column() {
  CardTitle()
  // 其他内容
}

带参组件:@Prop 与默认值

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

typescript 复制代码
@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)
  }
}

使用:

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

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

@Builder 复用 UI 片段

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

无参 Builder

typescript 复制代码
@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 定义占位,调用时用尾随闭包传入:

typescript 复制代码
@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:

typescript 复制代码
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 堆叠

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

typescript 复制代码
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 的容器中)。例如:

typescript 复制代码
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 抽公共样式,就能在保持结构清晰的前提下减少重复代码。

相关推荐
鸿蒙开发工程师—阿辉3 小时前
让 AI 帮你编译部署鸿蒙应用:harmonyos-build-deploy Skill
华为·harmonyos
盐焗西兰花3 小时前
鸿蒙学习实战之路-Reader Kit构建阅读器最佳实践
学习·华为·harmonyos
一起养小猫5 小时前
Flutter for OpenHarmony 实战:记忆棋游戏完整开发指南
flutter·游戏·harmonyos
飞羽殇情5 小时前
基于React Native鸿蒙跨平台开发构建完整电商预售系统数据模型,完成参与预售、支付尾款、商品信息展示等
react native·react.js·华为·harmonyos
Betelgeuse766 小时前
【Flutter For OpenHarmony】TechHub技术资讯界面开发
flutter·ui·华为·交互·harmonyos
国服第二切图仔7 小时前
openJiuwen智能体平台部署搭建及政务通助手工作流智能体开发实战
华为·政务·智能体
大雷神7 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地-- 第33篇:应用打包、签名与发布
华为·harmonyos
mocoding7 小时前
使用已经完成鸿蒙化适配的Flutter本地持久化存储三方库shared_preferences让你的应用能够保存用户偏好设置、缓存数据等
flutter·华为·harmonyos·鸿蒙
zhuweisky9 小时前
ArkTS实现鸿蒙手机视频聊天、屏幕分享(HarmonyOS)
音视频·harmonyos·鸿蒙开发