鸿蒙开发协调布局CollapsibleLayout

鸿蒙开发协调布局CollapsibleLayout

首先鸿蒙我暂时没找到官方提供的协调布局,所以得自己自定义。

一、思路

可滚动头部、粘性头部、可滚动内容布局
可折叠区域高度=可滚动头部高度-粘性头部高度

二、效果图

鸿蒙开发协调布局CollapsibleLayout

三、关键代码
// 联系:893151960
@Component
export struct CollapsibleLayout {
  @Builder AppBarInitBuilder() {}
  // 标题栏
  @BuilderParam AppBarBuilder: () => void = this.AppBarInitBuilder
  @Builder ScrollableHeaderInitBuilder() {}
  // 可滚动头部
  @BuilderParam ScrollableHeaderBuilder: () => void = this.ScrollableHeaderInitBuilder
  @Builder StickyHeaderInitBuilder() {}
  // 粘性头部
  @BuilderParam StickyHeaderBuilder: () => void = this.StickyHeaderInitBuilder
  // 页面内容部分,通常为列表
  @Builder ContentInitBuilder() {}
  @BuilderParam ContentBuilder: () => void = this.ContentInitBuilder
  @ObjectLink mediator: CollapsibleMediator

  // 标题栏高度
  @State appBarHeight: number = 0
  // 可滚动头部高度
  @State  scrollableHeaderHeight: number = 0
  // 粘性头部高度
  @State  stickyHeaderHeight: number = 0
  //  组件高度
  @State  totalHeight: number = 0
   lastProgress = 0
  showAppBar:boolean = true


  calculateContentHeight() {

    if (this.totalHeight != 0 && this.stickyHeaderHeight != 0 ) {
      return this.totalHeight - this.stickyHeaderHeight - this.appBarHeight
    }
    return 2000
  }

  /**
   * 计算可折叠区域高度、内容区域高度
   */
  calculateCoordinatorScrollableHeight() {
    if ( this.scrollableHeaderHeight != 0 && this.totalHeight != 0 && this.stickyHeaderHeight != 0) {
      this.mediator.coordinatorScrollableHeight = this.scrollableHeaderHeight - this.appBarHeight
    }
  }

  updateCollapsibleScrollProgress() {
    if (this.mediator.collapsibleScrollProgressCallback) {
      const progress = this.mediator.curCoordinatorOffset() / this.mediator.coordinatorScrollableHeight
      if (this.lastProgress != progress && Math.abs(this.lastProgress - progress) >= 0.01 || progress == 1) {
        // if (this.lastProgress != progress) {
        this.mediator.collapsibleScrollProgressCallback(progress)
        this.lastProgress = progress
      }
    }
  }

  @Builder BodyBuilder() {
    // 粘性头部
    Stack() {
      this.StickyHeaderBuilder()
    }.onAreaChange((_, newValue: Area) => {
      this.stickyHeaderHeight = newValue.height as number
      this.calculateCoordinatorScrollableHeight()
    })
    // .zIndex(100)

    Stack() {
      this.ContentBuilder()
    }.height(this.calculateContentHeight())
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      Scroll(this.mediator.outScroller) {
        Column() {
          // 可滚动容器
          Stack() {
            this.ScrollableHeaderBuilder()
          }.onAreaChange((_, newValue: Area) => {
            this.scrollableHeaderHeight = newValue.height as number
            this.calculateCoordinatorScrollableHeight()
          })

          this.BodyBuilder()
        }
      }
      .scrollable(ScrollDirection.Vertical)
      .scrollBar(BarState.Off)
      .edgeEffect(EdgeEffect.None)
      .height(this.totalHeight != 0 ? this.totalHeight : 10000)
      .onWillScroll(() => {
        this.updateCollapsibleScrollProgress()
      })
      .onScrollFrameBegin((offset: number, _) => {
        if (offset > 0) { // 向上
          if (this.mediator.isShrink()) { // 可折叠区域完全折叠
            return { offsetRemain: 0 }
          } else {
            return { offsetRemain: offset }
          }
        }
        if (offset < 0) { // 向下
          if (this.mediator.isExpand()) { // 可折叠区域完全张开
            return { offsetRemain: 0 }
          } else {
            return { offsetRemain: offset }
          }
        }
        return { offsetRemain: offset }
      })


      // 固定标题栏
      if (this.showAppBar){
        Stack() {
          this.AppBarBuilder()
        }.width("100%")
        .onAreaChange((_, newValue: Area) => {
          this.appBarHeight = newValue.height as number
          this.calculateCoordinatorScrollableHeight()
        })
      }


    }
    .width("100%")
    .onAreaChange((_, newValue: Area) => {
      this.totalHeight = newValue.height as number
      this.calculateCoordinatorScrollableHeight()
    })
  }
}

@Observed
export class CollapsibleMediator {
  // 内部可滚动容器集合
  innerScrollerArrays: Scroller[] = new Array<Scroller>()

  // 可折叠区域的高度(scrollableHeader-appBarHeight-stickyHeaderHeight)
  coordinatorScrollableHeight: number = 0
  curInnerScrollerIndex = 0


  // 可折叠区域滑动进度回调
  collapsibleScrollProgressCallback?: (progress: number) => void
  outScroller = new Scroller()

  constructor(coordinatorScrollProgressCallback?: (progress: number) => void ) {
    this.collapsibleScrollProgressCallback = coordinatorScrollProgressCallback
  }

  getCurrentInnerScroller(index?: number) {
    const curIndex = index ? index : 0
    if (this.innerScrollerArrays[curIndex] == null) {
      this.innerScrollerArrays[curIndex] = new Scroller()
    }
    return this.innerScrollerArrays[curIndex]
  }

  getCurrentInnerScrollerOffset() {
    let y = this.innerScrollerArrays?.[this.curInnerScrollerIndex]?.currentOffset()?.yOffset
    return (y ? y : 0) | 0
  }

  curCoordinatorOffset() {
    return this.outScroller.currentOffset().yOffset
  }

  getScrollerFrameRemainOffset(offset: number): number {
    if (!this.isShrink() && offset > 0) { // 向上
      this.outScroller.scrollBy(0, offset)
      return 0
    }
    if (this.getCurrentInnerScrollerOffset() == 0 && offset < 0) { // 向下
      this.outScroller.scrollBy(0, offset)
      return 0
    }
    return offset
  }

  setCurInnerScrollerIndex(index: number) {
    if (this.curInnerScrollerIndex !== index) {
      this.curInnerScrollerIndex = index
    }
  }

  isExpand() {
    return this.curCoordinatorOffset() === 0
  }

  isShrink() {
    return this.coordinatorScrollableHeight !== 0 && Math.abs(this.curCoordinatorOffset() - this.coordinatorScrollableHeight) <= 0.0001
  }

  expand() {
    this.outScroller.scrollTo({ xOffset: 0, yOffset: 0,
      animation: { duration: 300, curve: Curve.Linear } })
  }

  shrink() {
    this.outScroller.scrollTo({ xOffset: 0, yOffset: this.coordinatorScrollableHeight,
      animation: { duration: 300, curve: Curve.Linear } })
  }
}
四、项目demo结构图

在这里插入图片描述有需要完整源码可私信或者看昵称联系我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值