第二篇:自定义组件进阶
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)
}
}
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦