鸿蒙自定义组件开发完整指南【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

登录 后评论。没有帐号? 注册 一个。