HarmonyOS鸿蒙 Next 实现协调布局效果

news/2024/10/8 23:21:47/

HarmonyOS鸿蒙 Next 实现协调布局效果

​ 假期愉快! 最近大A 的涨势实在是红的让人晕头转向,不知道各位收益如何,这会是在路上,还是已经到目的地了?

言归正传,最近有些忙,关于鸿蒙的实践系列有些脱节了,趁着放假给大家上点干货.

我们先来看个效果 源码地址放这里了,想看具体实现的接着往下看 ↓↓↓↓

​ 做过Android开发的都知道 这个是Android Material Design中的 协调布局CoordinatorLayout的效果。协调布局能够让页面元素在滚动时动态响应,比如可滚动的头部、悬停的 Tab 栏以及可滚动的内容区域。
这种布局提升了用户的交互体验,特别是在内容较多且需要分段展示的场景下。

但是我们翻遍了 鸿蒙的ARK UI都没有这个组件和效果的实现。所以我们今天一起来实现一下这个效果.

首先我们分析下这个效果要实现的三个关键要素

  1. 可滚动的头部区域(如图片、标题等)。
  2. 粘性头部区域(Tab 页签等,当页面滑动到一定程度时需要悬停)。
  3. 可滚动的内容区域(具体的 Tab 页面内容,如列表或其他内容)。

接下来我们将详细介绍如何在 HarmonyOS Next 实现协调布局,包括滑动冲突的处理和粘性头部悬停的实现。


实现原理

协调布局的核心在于处理页面的多区域滑动冲突,并确保粘性头部在滚动时能够悬停。实现这种布局,需要使用以下几个关键组件:

  1. CoordinatorLayout:用于整体布局管理,处理可滚动的头部、粘性头部和内容区域的协调滚动。
  2. CollapsibleMediator:负责管理滚动状态,协调可滚动头部、粘性头部与内容区域的联动关系。
  3. 自定义 Builder:也就是我们上面所介绍的3个核心要素,实现不同布局部分的构建,如 ScrollableHeaderBuilderStickyHeaderBuilderContentBuilder

组件介绍

1. CoordinatorLayout 组件

CoordinatorLayout 是页面的主要布局容器,负责管理页面中不同区域(头部、粘性头部、内容区域)的布局和滚动逻辑。它的核心任务是根据用户的滚动行为动态调整各区域的显示状态,确保当页面滚动到某个位置时,粘性头部能够悬停。

@Component
export struct CoordinatorLayout {@BuilderParam ScrollableHeaderBuilder: () => void@BuilderParam StickyHeaderBuilder: () => void@BuilderParam ContentBuilder: () => void@ObjectLink mediator: CollapsibleMediatorbuild() {Stack({ alignContent: Alignment.Top }) {Scroll(this.mediator.outScroller) {Column() {// 可滚动的头部区域Stack() {this.ScrollableHeaderBuilder()}.onAreaChange((_, newValue: Area) => {this.mediator.scrollableHeaderHeight = newValue.height as number;this.mediator.calculateCoordinatorScrollableHeight();})// 粘性头部和内容区域this.BodyBuilder()}}.scrollable(ScrollDirection.Vertical).height(this.mediator.totalHeight != 0 ? this.mediator.totalHeight : 10000).onScrollFrameBegin((offset: number, _) => {return { offsetRemain: this.mediator.handleScroll(offset) };})}}@Builder BodyBuilder() {// 粘性头部区域Stack() {this.StickyHeaderBuilder()}.onAreaChange((_, newValue: Area) => {this.mediator.stickyHeaderHeight = newValue.height as number;this.mediator.calculateCoordinatorScrollableHeight();})// 内容区域Stack() {this.ContentBuilder()}.height(this.mediator.calculateContentHeight())}
}

2.CollapsibleMediator 组件

CollapsibleMediator 是协调滚动行为的核心组件。它管理了页面的滚动状态,处理各个区域的联动,确保粘性头部悬停和内容的平滑滚动。关键任务包括计算折叠区域高度、处理滚动进度,并在不同滚动方向时作出响应。

@Observed
export class CollapsibleMediator {innerScrollerArrays: Scroller[] = new Array<Scroller>()coordinatorScrollableHeight: number = 0curInnerScrollerIndex = 0collapsibleScrollProgressCallback?: (progress: number) => voidoutScroller = new Scroller()getCurrentInnerScroller(index: number) {if (!this.innerScrollerArrays[index]) {this.innerScrollerArrays[index] = new Scroller()}return this.innerScrollerArrays[index]}handleScroll(offset: number): number {if (offset > 0 && this.isShrink()) {return 0;} else if (offset < 0 && this.isExpand()) {return 0;}return offset;}isExpand() {return this.curCoordinatorOffset() === 0;}isShrink() {return Math.abs(this.curCoordinatorOffset() - this.coordinatorScrollableHeight) <= 0.0001;}curCoordinatorOffset() {return this.outScroller.currentOffset().yOffset;}calculateCoordinatorScrollableHeight() {if (this.scrollableHeaderHeight !== 0 && this.totalHeight !== 0 && this.stickyHeaderHeight !== 0) {this.coordinatorScrollableHeight = this.scrollableHeaderHeight - this.appBarHeight;}}calculateContentHeight() {if (this.totalHeight !== 0 && this.stickyHeaderHeight !== 0) {return this.totalHeight - this.stickyHeaderHeight;}return 2000;}
} 

3.滑动冲突的处理

在协调布局中,页面通常包含多个滚动区域,如可滚动的头部、粘性头部和可滚动的内容区域。这些区域之间的滑动冲突需要通过 CollapsibleMediator 来协调。
问题:滑动冲突

当页面有多个可滚动区域时,滚动冲突容易发生。例如,当用户向上滑动时,如何确定是滚动头部、粘性头部,还是内容区域?为了解决这些冲突,我们需要确保不同区域在特定条件下有不同的滚动响应。
解决方案:滚动优先级处理

CollapsibleMediator 通过监控滚动事件,根据滚动的方向和当前区域的状态来决定如何处理滚动:
向上滑动:如果粘性头部未完全折叠,则优先折叠头部;当头部完全折叠后,内容区域开始滚动。
向下滑动:如果内容区域已经滚动到顶部,则展开粘性头部;当粘性头部完全展开后,再展开可滚动头部。

关键逻辑:

 
handleScroll(offset: number): number {if (offset > 0) { // 向上滑动if (this.isShrink()) { // 当折叠区域完全折叠时return 0; // 停止滑动} else {return offset; // 继续折叠头部}} else if (offset < 0) { // 向下滑动if (this.isExpand()) { // 当折叠区域完全展开时return 0; // 停止滑动} else {return offset; // 继续展开头部}}return offset;
}

4.粘性头部悬停的判断

悬停效果的关键是 粘性头部区域(通常是滑动组件),在滚动时应固定在页面顶部。为了实现这一效果,CollapsibleMediator 会监听页面的滚动位置,当粘性头部到达顶部时,将其固定不再滚动。
悬停的核心逻辑:悬停的判断依赖于当前滚动偏移量和可折叠区域的高度。当滚动达到某个临界值时,粘性头部进入悬停状态:

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

当 curCoordinatorOffset() 等于 coordinatorScrollableHeight 时,表示头部区域已经折叠完毕,此时粘性头部应悬停。

自定义布局的使用

我们前面介绍的三要素当中,

可滚动的头部区域 以及粘性头部区域 直接使用普通组件即可,关于可滚动的内容区域 ,下面要着重做一下讲解,因为这块的滑动和CoordinatorLayout在外层的滑动存在着滑动冲突,所以我们在以下情况需要特殊处理:

  • 当外部容器未完全展开/收起时,优先处理外部容器的滚动。

  • 当外部容器已完全展开/收起时,内部列表可以正常滚动。

  • 在滚动过程中,可以平滑地过渡between外部容器和内部列表的滚动。

@Builder
ContentBuilder() {List({ scroller: this.collapsibleMediator.getCurrentInnerScroller(0) }) {ForEach(new Array<number>(10).fill(0).map((_, index: number) => index), (item: number) => {ListItem() {Text(`${"测试"}${item}`).width('100%').height(50).fontSize(16).textAlign(TextAlign.Center)}.height(180)})}.onScrollFrameBegin((offset: number, _) => {// 联动 CollapsibleMediator 处理滚动return { offsetRemain: this.collapsibleMediator?.getScrollerFrameRemainOffset(offset) };})
}

上述代码代码主要通过以下几个方面来处理滑动冲突:

  1. 使用 CollapsibleMediator:

this.collapsibleMediator 是一个 CollapsibleMediator 实例,用于协调外部滚动容器和内部滚动列表之间的滚动行为。

  1. 设置内部滚动器:

通过 scroller: this.collapsibleMediator.getCurrentInnerScroller(0) 将列表的滚动器与 CollapsibleMediator 关联起来。这样可以让 mediator 控制内部列表的滚动。

  1. 处理滚动事件:

.onScrollFrameBegin() 方法用于捕获每一帧的滚动事件。

  1. 计算剩余滚动量:

在滚动事件处理中,调用 this.collapsibleMediator?.getScrollerFrameRemainOffset(offset) 来计算实际应该滚动的距离。

  1. 返回剩余滚动量:

将计算得到的剩余滚动量作为 offsetRemain 返回,系统会根据这个值来决定实际的滚动行为。

整体实现效果

可滚动头部:通过 ScrollableHeaderBuilder 定义一个可滚动的头部区域,当用户滚动页面时,头部内容首先向上折叠。
粘性头部悬停:使用 StickyHeaderBuilder 创建粘性头部,包含 Tabs 组件。通过 CollapsibleMediator 的滚动逻辑,当用户滚动页面到达该区域时,粘性头部悬停在页面顶部。
可滚动内容区域:通过 ContentBuilder 创建内容区域,该区域在粘性头部悬停后继续滚动。

​ 本文介绍的实现方案不仅能够处理复杂的滑动冲突,还可以在不同区域间实现平滑的滚动体验和粘性头部的悬停效果。
这种布局在实际开发中非常有用,特别是在有多个滚动区域和悬停需求的场景中,如电商首页、新闻应用等。


http://www.ppmy.cn/news/1536354.html

相关文章

云原生架构师2024

├──2-Linux操作系统 | ├──1-项目部署之-Linux操作系统 | | ├──1-Linux概述与安装 | | ├──2-Linux基本操作 | | └──3-Linux软件安装与配置 | └──2-Shell编程 | | └──1-Shell编程 | ├──3-计算机网络基础 | | └──1-计算机…

贝锐蒲公英工业物联方案:助力美的智慧楼宇全球布局

智慧楼宇正日益成为现代城市发展的基石&#xff0c;作为该领域的先锋&#xff0c;美的楼宇科技通过其创新的iBUILDING数字化平台和低碳技术&#xff0c;引领着智慧空间的可持续发展&#xff0c;并持续推动建筑及相关行业的数字化转型。 美的楼宇科技的解决方案融合了先进的楼宇…

eNodeB User Manual - Troubleshooting

### COTS UE问题 以下是使用srsENB时最常见的问题&#xff1a; #### UE看不到网络 UE看不到网络的最可能原因是eNB/EPC配置、RF条件和使用的射频前端的频率精度。 首先检查您配置的LTE频段和EARFCN是否被您正在使用的UE支持。大多数UE设备支持LTE分配的频段的一个子集。确保…

React事件机制详解

React的事件机制详解如下&#xff1a; 1. 事件绑定 在React中&#xff0c;事件绑定是通过JSX语法实现的&#xff0c;例如使用onClick、onChange等属性来绑定点击事件或输入框内容改变事件等。 2. 事件处理程序 事件处理程序是在事件触发时执行的函数&#xff0c;这些函数被定义…

MyBatis 如何实现延迟加载?深度探讨 MyBatis 的延迟加载:如何优化数据访问效率

在当今的应用程序开发中&#xff0c;尤其是与数据库交互时&#xff0c;性能成为了重中之重。频繁的数据库访问会导致响应时间变慢&#xff0c;甚至影响用户体验。为了优化数据访问&#xff0c;MyBatis 提供了延迟加载&#xff08;Lazy Loading&#xff09;的强大功能。本文将详…

linux批量删文件

在 Linux 中&#xff0c;可以使用命令行工具来批量删除文件。以下是一些常用的方法&#xff1a; 使用 rm 命令 rm 是一个用于删除文件和目录的命令。使用此命令时应谨慎&#xff0c;因为删除操作是不可逆的。 删除特定类型的文件 例如&#xff0c;要删除当前目录下所有的 .tx…

Ascend C 自定义算子开发:高效的算子实现

Ascend C 自定义算子开发&#xff1a;高效的算子实现 在 Ascend C 平台上&#xff0c;开发自定义算子能够充分发挥硬件的性能优势&#xff0c;帮助开发者针对不同的应用场景进行优化。本文将以 AddCustom 算子为例&#xff0c;介绍 Ascend C 中自定义算子的开发流程及关键技术…

Spring Boot中线程池使用

说明&#xff1a;在一些场景&#xff0c;如导入数据&#xff0c;批量插入数据库&#xff0c;使用常规方法&#xff0c;需要等待较长时间&#xff0c;而使用线程池可以提高效率。本文介绍如何在Spring Boot中使用线程池来批量插入数据。 搭建环境 首先&#xff0c;创建一个Spr…