HarmonyOS ArkUI 组件开发实战:自定义组件与高级布局详解

HarmonyOS ArkUI 组件开发实战:自定义组件与高级布局详解## 一、前言ArkUI(方舟UI框架)是 HarmonyOS 应用开发的核心 UI 框架,提供声明式开发范式。掌握自定义组件和高级布局技术是开发高质量鸿蒙应用的关键。本文将以 HarmonyOS 5.0.0(API 12)为基础,深入讲解 ArkUI 自定义组件的开发要点,从生命周期、渲染控制到高级布局技巧,全部配合可运行的代码示例。## 二、自定义组件基础### 2.1 基本结构每个自定义组件由 struct + @Component 装饰器组成,必须实现 build() 方法:@Component

struct MyComponent {

@State message: string = 'Hello ArkUI';

build() {

Column() {

Text(this.message)

.fontSize(24)

.fontWeight(FontWeight.Bold)

复制代码
  Button('点击更新')
    .onClick(() => {
      this.message = 'Hello HarmonyOS!';
    })
}
.padding(20)

}

}

@Entry

@Component

struct Index {

build() {

MyComponent()

}

}

2.2 @Entry 与 @Component 的区别| 装饰器 | 作用 | 关键约束 ||--------|------|---------|| @Entry | 标记页面入口 | 仅单个页面中可使用一个,根节点必须是容器组件 || @Component | 标记可复用组件 | 无限制,根节点可以是任意组件 |### 2.3 组件嵌套与参数传递@Component

struct GreetingCard {

@Prop name: string = '';

@Prop age: number = 0;

@Prop avatarColor: ResourceColor = Color.Gray;

build() {

Column({ space: 12 }) {

Circle()

.width(60).height(60)

.fill(this.avatarColor)

复制代码
  Text(`你好,我是 ${this.name}`)
    .fontSize(18).fontWeight(FontWeight.Medium)

  Text(`${this.age} 岁`)
    .fontSize(14).fontColor('#888')
}
.padding(20)
.backgroundColor('#F8F8F8')
.borderRadius(16)
.alignItems(HorizontalAlign.Center)

}

}

@Entry

@Component

struct CardList {

build() {

Scroll() {

Column({ space: 16 }) {

GreetingCard({ name: '小明', age: 25, avatarColor: '#4CAF50' })

GreetingCard({ name: '小红', age: 23, avatarColor: '#FF9800' })

GreetingCard({ name: '小刚', age: 28, avatarColor: '#2196F3' })

}

.padding(20)

}

.width('100%').height('100%')

}

}

三、组件生命周期ArkUI 自定义组件具有完整的生命周期回调:@Component

struct LifecycleDemo {

@State count: number = 0;

aboutToAppear() {

console.info('组件即将出现');

// 适合执行初始化操作:加载数据、注册监听等

}

aboutToDisappear() {

console.info('组件即将销毁');

// 适合执行清理操作:取消监听、释放资源等

}

onDidBuild() {

console.info('组件布局完成');

}

build() {

Column({ space: 20 }) {

Text(计数: ${this.count}).fontSize(32)

Button('增加计数').fontSize(18).onClick(() => { this.count++ })

Text('查看控制台日志').fontSize(14).fontColor('#999')

}

.width('100%').padding(20)

}

}

页面生命周期(@Entry 组件专用)@Entry

@Component

struct PageLifecycle {

@State pageContent: string = '';

onPageShow() {

console.info('页面显示');

this.pageContent = '页面已显示';

}

onPageHide() {

console.info('页面隐藏');

}

onBackPress(): boolean {

console.info('用户按下返回键');

return false; // false 让系统处理默认返回行为

}

build() {

Column({ space: 20 }) {

Text('页面生命周期示例').fontSize(24).fontWeight(FontWeight.Bold)

Text(this.pageContent).fontSize(16).fontColor('#007AFF')

}

.width('100%').height('100%').justifyContent(FlexAlign.Center).padding(20)

}

}

四、渲染控制### 4.1 条件渲染:if/else@Component

struct ConditionalRender {

@State isLoggedIn: boolean = false;

@State username: string = '';

build() {

Column({ space: 20 }) {

if (this.isLoggedIn) {

Column({ space: 10 }) {

Text(欢迎回来,${this.username}).fontSize(20).fontWeight(FontWeight.Bold)

Button('退出登录').fontSize(16).backgroundColor('#FF4444')

.onClick(() => { this.isLoggedIn = false; this.username = '' })

}

} else {

Column({ space: 10 }) {

Text('请登录').fontSize(20).fontColor('#888')

Button('模拟登录').fontSize(16).backgroundColor('#007AFF')

.onClick(() => { this.isLoggedIn = true; this.username = 'HarmonyOS 开发者' })

}

}

}

.width('100%').padding(20)

}

}

4.2 循环渲染:ForEachinterface TaskItem {

id: number;

title: string;

priority: 'high' | 'medium' | 'low';

completed: boolean;

}

@Component

struct ForEachDemo {

@State tasks: TaskItem\[\] = [

{ id: 1, title: '完成需求文档', priority: 'high', completed: false },

{ id: 2, title: 'Code Review', priority: 'medium', completed: true },

{ id: 3, title: '更新依赖库', priority: 'low', completed: false },

{ id: 4, title: '编写测试用例', priority: 'high', completed: false },

{ id: 5, title: '优化性能', priority: 'medium', completed: false }

];

getPriorityColor(priority: string): ResourceColor {

switch (priority) {

case 'high': return '#FF4444';

case 'medium': return '#FF9800';

default: return '#4CAF50';

}

}

build() {

Column({ space: 16 }) {

Text('任务列表').fontSize(24).fontWeight(FontWeight.Bold)

复制代码
  List({ space: 8 }) {
    ForEach(this.tasks, (item: TaskItem, index: number) => {
      ListItem() {
        Row({ space: 12 }) {
          Circle().width(12).height(12).fill(this.getPriorityColor(item.priority))
          Text(item.title)
            .fontSize(16)
            .decoration({ type: item.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
            .fontColor(item.completed ? '#999' : '#333')
            .layoutWeight(1)
          Text(item.completed ? '✅' : '⏳').fontSize(18)
        }
        .width('100%').padding(12).backgroundColor('#FAFAFA').borderRadius(8)
      }
    }, (item: TaskItem) => item.id.toString())
  }
  .layoutWeight(1).width('100%')
}
.width('100%').height('100%').padding(20)

}

}

重要 :ForEach 的第三个参数 keyGenerator 必须提供唯一且稳定的 key。### 4.3 懒加载:LazyForEachclass StringDataSource implements IDataSource {

private dataArray: string\[\] = \[\];

private listeners: DataChangeListener\[\] = \[\];

constructor(data: string\[\]) {

for (let i = 0; i < data.length; i++) {

this.dataArray.push(datai);

}

}

totalCount(): number { return this.dataArray.length; }

getData(index: number): string { return this.dataArrayindex; }

registerDataChangeListener(listener: DataChangeListener): void {

if (this.listeners.indexOf(listener) < 0) this.listeners.push(listener);

}

unregisterDataChangeListener(listener: DataChangeListener): void {

const pos = this.listeners.indexOf(listener);

if (pos >= 0) this.listeners.splice(pos, 1);

}

}

@Component

struct LazyForEachDemo {

private dataSource: StringDataSource;

aboutToAppear() {

const bigData: string\[\] = \[\];

for (let i = 0; i < 10000; i++) bigData.push(数据项 #${i + 1});

this.dataSource = new StringDataSource(bigData);

}

build() {

Column() {

Text('LazyForEach 懒加载列表').fontSize(20).fontWeight(FontWeight.Bold).padding(16)

List() {

LazyForEach(this.dataSource, (item: string, index: number) => {

ListItem() { Text(item).fontSize(16).width('100%').padding(12) }

}, (item: string) => item)

}

.layoutWeight(1)

}

.width('100%').height('100%')

}

}

五、@Builder 装饰器:构建可复用的 UI 片段### 5.1 @Builder 基本用法@Component

struct BuilderDemo {

@State selectedTab: number = 0;

@Builder

tabItem(index: number, label: string, icon: string) {

Column({ space: 4 }) {

Text(icon).fontSize(24).opacity(this.selectedTab === index ? 1.0 : 0.5)

Text(label).fontSize(12).fontColor(this.selectedTab === index ? '#007AFF' : '#999')

}

.padding({ top: 8, bottom: 8 })

.onClick(() => { this.selectedTab = index })

.layoutWeight(1)

}

build() {

Column() {

Text(当前选中: Tab ${this.selectedTab})

.fontSize(20).layoutWeight(1).width('100%').textAlign(TextAlign.Center)

复制代码
  Row() {
    this.tabItem(0, '首页', '🏠')
    this.tabItem(1, '搜索', '🔍')
    this.tabItem(2, '通知', '🔔')
    this.tabItem(3, '我的', '👤')
  }
  .width('100%').height(56).backgroundColor('#FFFFFF')
  .shadow({ radius: 4, color: '#10000000', offsetX: 0, offsetY: -2 })
}
.width('100%').height('100%')

}

}

5.2 @BuilderParam:传递 @Builder 函数@Component

struct CustomCard {

@Prop title: string = '';

@BuilderParam contentBuilder: () => void = this.defaultContent;

@Builder defaultContent() {

Text('默认内容').fontSize(14).fontColor('#999')

}

build() {

Column({ space: 12 }) {

Text(this.title).fontSize(18).fontWeight(FontWeight.Bold)

Divider()

this.contentBuilder()

}

.width('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(12)

.shadow({ radius: 8, color: '#10000000' })

}

}

@Entry

@Component

struct CardPage {

@Builder userProfileContent() {

Row({ space: 12 }) {

Circle().width(48).height(48).fill('#2196F3')

Column({ space: 4 }) {

Text('张三').fontSize(16).fontWeight(FontWeight.Medium)

Text('高级软件开发工程师').fontSize(12).fontColor('#888')

}

}

}

@Builder statsContent() {

Row({ space: 0 }) {

Column({ space: 4 }) {

Text('128').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#007AFF')

Text('项目').fontSize(12).fontColor('#888')

}.layoutWeight(1)

Column({ space: 4 }) {

Text('3.6K').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#4CAF50')

Text('粉丝').fontSize(12).fontColor('#888')

}.layoutWeight(1)

Column({ space: 4 }) {

Text('892').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#FF9800')

Text('点赞').fontSize(12).fontColor('#888')

}.layoutWeight(1)

}

}

build() {

Scroll() {

Column({ space: 16 }) {

CustomCard({ title: '用户信息', contentBuilder: this.userProfileContent })

CustomCard({ title: '统计数据', contentBuilder: this.statsContent })

}.padding(16)

}

.width('100%').height('100%').backgroundColor('#F5F5F5')

}

}

六、@Styles 和 @Extend:样式复用### 6.1 @Styles:定义可复用样式@Component

struct StylesDemo {

@Styles cardStyle() {

.width('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(12)

.shadow({ radius: 4, color: '#10000000' })

}

@Styles primaryButtonStyle() {

.fontSize(16).fontWeight(FontWeight.Medium).backgroundColor('#007AFF').borderRadius(8)

}

@Styles dangerButtonStyle() {

.fontSize(16).fontWeight(FontWeight.Medium).backgroundColor('#FF4444').borderRadius(8)

}

build() {

Column({ space: 16 }) {

Column({ space: 12 }) {

Text('样式复用示例').fontSize(20).fontWeight(FontWeight.Bold)

Text('@Styles 让你避免重复写相同的样式代码').fontSize(14).fontColor('#666')

}.cardStyle()

复制代码
  Row({ space: 12 }) {
    Button('确认').primaryButtonStyle().layoutWeight(1)
    Button('删除').dangerButtonStyle().layoutWeight(1)
  }.width('100%')
}.padding(16).width('100%')

}

}

6.2 @Extend:扩展系统组件样式@Extend(Text)

function headingText(fontSize: number = 24) {

.fontSize(fontSize).fontWeight(FontWeight.Bold).fontColor('#333')

}

@Extend(Text)

function descText() {

.fontSize(14).fontColor('#888').maxLines(2)

.textOverflow({ overflow: TextOverflow.Ellipsis })

}

@Extend(Button)

function roundedButton() {

.fontSize(16).fontWeight(FontWeight.Medium).borderRadius(24).height(48)

}

@Component

struct ExtendDemo {

build() {

Column({ space: 20 }) {

Text('HarmonyOS 开发指南').headingText(28)

Text('HarmonyOS 是一款面向万物互联时代的分布式操作系统,它不仅仅是一个手机操作系统,更是一个全场景的智能终端操作系统。').descText()

Button('了解更多').roundedButton().backgroundColor('#007AFF').width('100%')

}.padding(20)

}

}

七、高级组件实战:下拉刷新interface NewsData { title: string; summary: string; time: string; readCount: number }

@Component

struct NewsItem {

@Prop title: string = '';

@Prop summary: string = '';

@Prop time: string = '';

@Prop readCount: number = 0;

build() {

Column({ space: 8 }) {

Text(this.title).fontSize(16).fontWeight(FontWeight.Medium).maxLines(2)

.textOverflow({ overflow: TextOverflow.Ellipsis })

Text(this.summary).fontSize(14).fontColor('#666').maxLines(2)

.textOverflow({ overflow: TextOverflow.Ellipsis })

Row({ space: 12 }) {

Text(this.time).fontSize(12).fontColor('#999')

Text(${this.readCount} 阅读).fontSize(12).fontColor('#999')

}

}.width('100%').padding(12)

}

}

@Entry

@Component

struct NewsList {

@State newsList: NewsData\[\] = \[\];

@State isRefreshing: boolean = false;

aboutToAppear() { this.loadData() }

loadData() {

this.newsList = [

{ title: '鸿蒙生态设备突破 10 亿台', summary: '华为宣布鸿蒙生态设备数量已突破 10 亿台', time: '2小时前', readCount: 12500 },

{ title: 'ArkTS 3.0 新特性解读', summary: 'ArkTS 3.0 带来了全新的并发模型和类型系统增强', time: '4小时前', readCount: 8900 },

{ title: 'DevEco Studio 5.0 发布', summary: '全新的开发工具体验,支持 AI 辅助编程', time: '6小时前', readCount: 6700 },

{ title: '鸿蒙应用上架指南', summary: '详细讲解鸿蒙应用上架 AppGallery 的完整流程', time: '昨天', readCount: 15000 },

{ title: 'OpenHarmony 5.0 路线图', summary: '开源鸿蒙 5.0 版本规划和技术预览', time: '2天前', readCount: 9800 }

];

}

handleRefresh() {

this.isRefreshing = true;

setTimeout(() => { this.isRefreshing = false }, 1500);

}

build() {

Column() {

Row() { Text('📰 科技资讯').fontSize(24).fontWeight(FontWeight.Bold) }

.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 12 }).backgroundColor('#FFFFFF')

复制代码
  Refresh({ refreshing: $$this.isRefreshing }) {
    List({ space: 1 }) {
      ForEach(this.newsList, (item: NewsData) => {
        ListItem() {
          NewsItem({ title: item.title, summary: item.summary, time: item.time, readCount: item.readCount })
        }
      }, (item: NewsData) => item.title)
    }
  }
  .onRefreshing(() => { this.handleRefresh() })
  .layoutWeight(1)
}
.width('100%').height('100%')

}

}

八、动画:animateTo@Entry

@Component

struct AnimationDemo {

@State scaleValue: number = 1;

@State rotateAngle: number = 0;

@State isExpanded: boolean = false;

build() {

Column({ space: 30 }) {

Text('ArkUI 动画示例').fontSize(24).fontWeight(FontWeight.Bold)

复制代码
  // 缩放动画
  Column({ space: 12 }) {
    Circle().width(80).height(80).fill('#007AFF').scale({ x: this.scaleValue, y: this.scaleValue })
    Button('缩放动画').fontSize(14).onClick(() => {
      animateTo({ duration: 500, curve: Curve.EaseInOut }, () => {
        this.scaleValue = this.scaleValue === 1 ? 1.5 : 1;
      })
    })
  }

  // 旋转动画
  Column({ space: 12 }) {
    Rectangle().width(80).height(80).fill('#FF9800').rotate({ angle: this.rotateAngle })
    Button('旋转动画').fontSize(14).onClick(() => {
      animateTo({ duration: 800, curve: Curve.EaseOut }, () => {
        this.rotateAngle += 180;
      })
    })
  }

  // 展开/折叠
  Column({ space: 12 }) {
    Button('展开/折叠').fontSize(14).onClick(() => {
      animateTo({ duration: 400, curve: Curve.FastOutSlowIn }, () => {
        this.isExpanded = !this.isExpanded;
      })
    })

    if (this.isExpanded) {
      Column({ space: 8 }) {
        Text('展开的内容区域').fontSize(16)
        Text('这里可以放更多信息...').fontSize(14).fontColor('#666')
      }
      .padding(16).backgroundColor('#F0F8FF').borderRadius(8).width('100%')
      .transition({ type: TransitionType.Insert, opacity: 0, translate: { y: -20 } })
    }
  }
}
.width('100%').padding(20)

}

}

九、build() 函数约束总结| 操作 | 是否允许 ||------|---------|| 使用 if/else 条件判断 | ✅ || 使用 ForEach/LazyForEach | ✅ || 调用 @Builder 方法 | ✅ || 声明本地变量 (let) | ❌ || 直接使用 console.info | ❌ || 使用 switch 语句 | ❌ || 创建本地作用域 {} | ❌ || 直接修改状态变量 | ❌ || 组件参数使用 TS 方法返回值 | ✅ |## 十、总结| 技术点 | 关键 API | 用途 ||--------|----------|------|| 自定义组件 | @Component / @Entry | 创建可复用 UI 组件 || 生命周期 | aboutToAppear / aboutToDisappear | 初始化和清理 || 渲染控制 | if/else / ForEach / LazyForEach | 条件渲染和列表渲染 || UI 复用 | @Builder / @BuilderParam | 提取可复用的 UI 片段 || 样式复用 | @Styles / @Extend | 避免样式代码重复 || 动画 | animateTo | 实现流畅过渡动画 |**开发建议:**1. 将复杂 UI 拆分为独立的自定义组件2. 使用 @BuilderParam 让组件更灵活3. 大数据列表必须使用 LazyForEach4. @Extend 比 @Styles 更适合扩展系统组件5. 动画过渡使用 animateTo,配合 Curve 实现自然效果> 参考文档:华为开发者联盟 HarmonyOS 5.0.0 API 12 --- ArkUI 组件开发指南

相关推荐
IT大白鼠1 小时前
BGP多归属技术原理与应用实践
网络·网络协议·华为
祭曦念1 小时前
鸿蒙Next实战-笑话大全App开发
华为·harmonyos
三声三视1 小时前
Electron 鸿蒙快捷键全失灵,我排查了六个小时
华为·electron·harmonyos·鸿蒙
风华圆舞1 小时前
鸿蒙构建失败时,先查 Flutter 还是先查 Hvigor
flutter·华为·harmonyos
YM52e1 小时前
鸿蒙HarmonyOS ArkTS 实战:教师座椅出入记录 APP 从零到一
学习·华为·harmonyos·鸿蒙系统
狼哥16862 小时前
蛋糕美食元服务_订单实现指南
ui·harmonyos
Swift社区2 小时前
鸿蒙游戏如何实现多端一致性?
游戏·华为·harmonyos
木咺吟2 小时前
【鸿蒙原生应用开发实战】第一篇:项目初始化与架构设计——从零搭建“阅迹“阅读应用
华为·harmonyos
组合缺一2 小时前
SolonCode(编码智能体)支持鸿蒙 PC
java·华为·ai·ai编程·harmonyos·solon·soloncode