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