当前位置: 首页 > article >正文

鸿蒙开发协调布局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结构图

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


http://www.kler.cn/a/309906.html

相关文章:

  • API接口精准获取商品详情信息案例
  • 移动开发(七):.NET MAUI使用RESTAPI实现查询天气笔记
  • 口子查好做吗?有什么特点?
  • “2048”游戏网页版html+css+js
  • 谷粒商城-高级篇-认证服务
  • 二分查找排序js
  • Unity3d 以鼠标位置点为中心缩放视角(正交模式下)
  • 待办: 杂七杂八——大杂烩.....懒得整理了,我自己凑合看
  • 新手学习Python第七天-新手笔记
  • 基于STM32C8T6的CubeMX:HAL库点亮LED
  • Datawhale X 李宏毅苹果书 AI夏令营 《深度学习详解》第十九章 ChatGPT
  • Python 入门教程(3)基础知识 | 3.6、标准输入与输出
  • c++----模板(进阶)
  • 什么是VHDX文件?
  • 国科云域名解析课堂:一个域名可以解析到多个IP地址吗?
  • 高校能耗管控方案如何做到节能减排
  • 【Python123题库】#绘制温度曲线 #XRD谱图绘制 #态密度曲线绘制
  • 3个WebSocket的.Net开源项目
  • 基于vue框架的宠物店管理系统的设计与实现4czn0(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
  • StackTrace在.Net中获取当前线程的堆栈跟踪信息
  • 【MySQL】MySQL连接池原理与简易网站数据流动是如何进行
  • mysql笔记7(单表查询)
  • 计算机毕业设计 健身房管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • Github 2024-09-12 Go开源项目日报Top10
  • 智能车镜头组入门(二)电路硬件的设计
  • mysql的高级查询:函数的运用(日期/数字/字符/聚合)及分组查询