鸿蒙自定义组件开发完整指南【2】
2025-06-28 11:08:59
108次阅读
0个评论
第二篇:自定义组件进阶
2.1 自定义布局
当系统提供的标准布局(如Column、Row、Flex等)无法满足我们的需求时,就需要使用自定义布局。这就像是建筑师需要设计特殊形状的房间时,不能只用标准的长方形,而需要自己测量和安排每个部件的位置。
自定义布局的两个核心方法:
- onMeasureSize:测量阶段,确定组件和子组件的尺寸
- onPlaceChildren:布局阶段,确定每个子组件的具体位置
应用场景:
- 流式布局(标签云、瀑布流)
- 自适应网格布局
- 复杂的卡片排列
- 特殊的动画布局
下面我们实现一个流式布局组件,它能够自动换行排列子组件:
@Component
struct FlowLayout {
@Builder
doNothingBuilder() {}
@BuilderParam builder: () => void = this.doNothingBuilder
@State itemSpacing: number = 8
@State lineSpacing: number = 8
onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions) {
let maxWidth = constraint.maxWidth as number
let currentLineWidth = 0
let totalHeight = 0
let lineHeight = 0
children.forEach((child) => {
let childResult: MeasureResult = child.measure({
minHeight: 0,
minWidth: 0,
maxWidth: maxWidth,
maxHeight: constraint.maxHeight
})
if (currentLineWidth + childResult.width > maxWidth) {
// 换行
totalHeight += lineHeight + this.lineSpacing
currentLineWidth = childResult.width + this.itemSpacing
lineHeight = childResult.height
} else {
currentLineWidth += childResult.width + this.itemSpacing
lineHeight = Math.max(lineHeight, childResult.height)
}
})
totalHeight += lineHeight
return {
width: maxWidth,
height: totalHeight
}
}
onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) {
let maxWidth = constraint.maxWidth as number
let currentX = 0
let currentY = 0
let lineHeight = 0
children.forEach((child) => {
if (currentX + child.measureResult.width > maxWidth) {
// 换行
currentX = 0
currentY += lineHeight + this.lineSpacing
lineHeight = 0
}
child.layout({ x: currentX, y: currentY })
currentX += child.measureResult.width + this.itemSpacing
lineHeight = Math.max(lineHeight, child.measureResult.height)
})
}
build() {
this.builder()
}
}
2.2 组件生命周期
组件生命周期就像是人的生老病死一样,每个组件从创建到销毁都会经历特定的阶段。了解和正确使用生命周期方法,可以帮我们在合适的时机执行特定的操作,比如数据加载、资源清理等。
主要的生命周期方法:
- aboutToAppear:组件即将出现,适合进行初始化操作
- aboutToDisappear:组件即将销毁,适合进行清理操作
- onPageShow:页面显示时触发(仅限@Entry组件)
- onPageHide:页面隐藏时触发(仅限@Entry组件)
使用场景示例:
- 在aboutToAppear中加载数据
- 在aboutToDisappear中取消网络请求、清理定时器
- 在onPageShow中刷新数据或恢复动画
- 在onPageHide中暂停动画或保存状态
@Component
struct LifecycleDemo {
@State data: string[] = []
@State isLoading: boolean = true
aboutToAppear() {
console.log('组件即将出现')
this.loadData()
}
aboutToDisappear() {
console.log('组件即将销毁')
// 清理资源,比如取消网络请求、清除定时器等
}
onPageShow() {
console.log('页面显示')
// 页面重新显示时可以刷新数据
}
onPageHide() {
console.log('页面隐藏')
// 页面隐藏时可以暂停一些操作
}
private async loadData() {
try {
// 模拟数据加载过程
await new Promise(resolve => setTimeout(resolve, 1000))
this.data = ['数据1', '数据2', '数据3']
this.isLoading = false
} catch (error) {
console.error('数据加载失败:', error)
this.isLoading = false
}
}
build() {
Column() {
if (this.isLoading) {
Text('加载中...')
.fontSize(16)
} else {
ForEach(this.data, (item: string) => {
Text(item)
.padding(12)
.backgroundColor('#F3F4F6')
.borderRadius(8)
.margin({ bottom: 8 })
})
}
}
.width('100%')
.padding(20)
}
}
2.3 组件通信
在复杂的应用中,组件之间需要进行数据交换和事件通知。鸿蒙提供了多种组件通信方式,就像是不同房间之间的对讲系统,让组件能够相互协调工作。
主要通信方式:
- **@Link**:双向数据绑定,适用于父子组件需要共享状态的场景
- 回调函数:子组件通过回调通知父组件发生的事件
- **@Provide/@Consume**:跨级组件通信,适用于祖孙组件之间的数据传递
- 事件总线:全局事件通信,适用于任意组件之间的消息传递
父子组件双向通信示例
下面的例子展示了一个选择器组件,父组件可以获取子组件的选择结果,子组件也可以修改父组件的状态:
// 子组件 - 选择器
@Component
export struct ChildComponent {
@Link selectedValue: string // 双向绑定,可以修改父组件的数据
@Prop options: string[] // 接收选项列表
onValueChange?: (value: string) => void // 回调函数,通知父组件
build() {
Column() {
ForEach(this.options, (option: string) => {
Text(option)
.padding(12)
.backgroundColor(this.selectedValue === option ? '#3B82F6' : '#F3F4F6')
.fontColor(this.selectedValue === option ? Color.White : Color.Black)
.borderRadius(8)
.margin({ bottom: 8 })
.onClick(() => {
this.selectedValue = option // 修改父组件的状态
if (this.onValueChange) {
this.onValueChange(option) // 通知父组件
}
})
})
}
}
}
// 父组件
@Component
struct ParentComponent {
@State currentSelection: string = ''
private options = ['选项1', '选项2', '选项3']
build() {
Column() {
Text(`当前选择: ${this.currentSelection}`)
.fontSize(18)
.margin({ bottom: 20 })
ChildComponent({
selectedValue: $currentSelection, // 使用$符号进行双向绑定
options: this.options,
onValueChange: (value: string) => {
console.log('用户选择了:', value)
// 可以在这里处理选择事件,比如发送网络请求等
}
})
}
.padding(20)
}
}
00
- 0回答
- 0粉丝
- 0关注
相关话题
- 鸿蒙自定义组件开发完整指南【3】
- 鸿蒙自定义组件开发完整指南【1】
- 鸿蒙自定义组件生命周期
- 鸿蒙无障碍开发完整指南【1】
- @ComponentV2装饰器:自定义组件
- 自定义组件之<二>自定义圆环(Ring)
- 自定义组件之<七>自定义组件之插槽(slot)
- 自定义组件之<八>自定义下拉刷新(RefreshList)
- 自定义组件之<三>自定义标题栏(TitleBar)
- 自定义组件之<四>自定义对话框(Dialog)
- 自定义组件之<五>自定义对话框(PromptAction)
- 自定义组件之<六>自定义饼状图(PieChart)
- HarmonyOS NEXT 鸿蒙实现自定义组件插槽
- 自定义组件之<九>自定义下拉刷新上拉加载(RefreshLayout)
- 鸿蒙开发:自定义一个剪辑双滑块组件