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

news2024/10/2 17:24:49

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: CollapsibleMediator

  build() {
    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 = 0
  curInnerScrollerIndex = 0
  collapsibleScrollProgressCallback?: (progress: number) => void
  outScroller = 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.coloradmin.cn/o/2184777.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Electron 是如何工作的

1. 创建electron项目 pnpm init pnpm add -D electron修改配置项 package.json {"name": "electron-menu","version": "1.0.0","description": "","main": "main.js", // eletron入口&quo…

【重学 MySQL】四十七、表的操作技巧——修改、重命名、删除与清空

【重学 MySQL】四十七、表的操作技巧——修改、重命名、删除与清空 修改表添加字段语法示例注意事项 删除字段语法示例 修改字段使用 MODIFY COLUMN语法示例 使用 CHANGE COLUMN语法示例 重命名表语法示例 删除表语法示例 清空表使用 TRUNCATE TABLE使用 DELETE FROM对比 TRUNC…

聊聊晶圆厂中的常见口语(1)

知识星球里的学员问&#xff1a;半导体公司的工程师总爱用一些英语代替中文&#xff0c;比如care,show&#xff0c;用这种简单的单词代替中文&#xff0c;能不能给我们总结工程师常用的英语单词&#xff0c;比较口语化的&#xff01; 为什么晶圆厂会用很多英文口语&#xff1f…

华为---以太网静态路由配置使用下一跳通信正常,而使用出接口无法通信

目录 1. 实验环境 2. 结果测试 3. 分析验证 3.1 以太网静态路由配置使用下一跳跨网段通信抓包分析 3.2 以太网静态路由配置使用出接口跨网段通信抓包分析 3.3 以太网静态路由配置使用出接口无法跨网段通信问题解决办法 1. 实验环境 以太网静态路由配置使用下一跳跨网段通…

番茄成熟度检测系统源码分享

番茄成熟度检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

Opencv第十一章——视频处理

1. 读取并显示摄像头视频 1.1 VideoCapture类 VideoCapture类提供了构造方法VideoCapture(),用于完成摄像头的初始化工作&#xff0c;其语法格式如下&#xff1a; capture cv2.VideoCapture(index) 参数说明&#xff1a; capture:要打开的摄像头视频。 index:摄像头设备索引。…

【区间dp】AT_dp_l 题解

题意 给一个双端队列&#xff0c;双方轮流取数&#xff0c;每一次能且只能从队头或队尾取数&#xff0c;取完数后将这个数从队列中弹出。双方都希望自己取的所有数之和尽量大&#xff0c;且双方都以最优策略行动&#xff0c;假设先手取的所有数之和为 X X X&#xff0c;后手取…

【Git】一文看懂Git

Git 一、简介1. Git 与 SVN 区别1.1 Git 是分布式的&#xff0c;SVN 不是1.1.1 分布式版本控制系统Git1.1.2 集中式版本控制系统SVN 1.2 Git 把内容按元数据方式存储&#xff0c;而 SVN 是按文件1.3 Git 分支和 SVN 的分支不同1.4 Git 没有一个全局的版本号&#xff0c;而 SVN …

五.运输层

目录 5.1概述 5.2传输层的寻址与端口 熟知端口号 套接字(Socket) 5.3 UDP 特点 UDP报文格式 UDP校验 二进制反码求和 5.4 TCP 特点 可靠传输 停止等待协议 流水线方式 累计应答 流量控制 滑动窗口 拥塞控制 三次握手&#xff0c;四次握手 5.1概述 只有主机…

Pikachu-Cross-Site Scripting-反射型xss(get)

存储型XSS 存储型XSS是指恶意脚本被存储在目标服务器上&#xff0c;当用户访问包含该脚本的页面时&#xff0c;脚本会被执行。攻击者通常通过输入框、留言板等用户可输入的地方进行注入。例如&#xff0c;攻击者可以在留言板中输入恶意脚本&#xff0c;当其他用户查看留言时&a…

3.基于分数的生成模型

1.简介 基于分数的生成模型(SGM)的核心是Stein分数(或分数函数)。给定一个概率密度函数p(x)&#xff0c;其分数函数定义为对数概率密度的梯度Vxlogp(x)。生成模型通过学习并建模输入数据的分布&#xff0c;从而采集生成新的样木&#xff0c;该模型广泛运用于图片视频生成、文本…

假期惊喜,收到公司款项86167.14元

假期惊喜 近日&#xff0c;有网友爆料称&#xff0c;比亚迪在未提前通知员工的情况下&#xff0c;突然发放了利润奖金。 有人获得了七八万元&#xff0c;也有人拿到了十多万元。 一位比亚迪员工的帖子显示&#xff0c;在9月26日下午&#xff0c;他的银行卡突然收到一笔 86167.1…

数字化那点事:一文读懂数字孪生

一、数字孪生的定义 数字孪生&#xff08;Digital Twin&#xff09;是指通过数字技术构建的物理实体的虚拟模型&#xff0c;能够对该实体进行全方位、动态跟踪和仿真预测。简单来说&#xff0c;数字孪生就是在一个设备或系统的基础上创造一个数字版的“克隆体”&#xff0c;这…

Redis --- 第二讲 --- 特性和安装

一、背景知识 Redis特性&#xff1a; Redis是一个在内存中存储数据的中间件&#xff0c;用于作为数据库&#xff0c;作为缓存&#xff0c;在分布式系统中能够大展拳脚。Redis的一些特性造就了现在的Redis。 在内存中存储数据&#xff0c;通过一系列的数据结构。MySQL主要是通…

Ollama安装部署CodeGeeX4 - ALL - 9B

一、模型本地部署准备 1、 conda create -n ollama python3.82、 curl -fsSL https://ollama.com/install.sh | sh3、验证安装 安装完成后&#xff0c;通过运行以下命令来验证Ollama是否正确安装&#xff1a; ollama --version4、启动ollama ollama serve模型地址&#xff…

【重学 MySQL】四十八、DCL 中的 commit 和 rollback

【重学 MySQL】四十八、DCL 中的 commit 和 rollback commit的定义与作用rollback的定义与作用使用场景相关示例注意事项DDL 和 DML 的说明 在MySQL中&#xff0c;DCL&#xff08;Data Control Language&#xff0c;数据控制语言&#xff09;用于管理数据库用户和控制数据的访问…

Ubuntu 安装RUST

官方给的是这样如下脚本 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 太慢了 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh -x 执行这个脚本后会给出对应的下载链接 如下图 我直接给出来 大多数应该都是这个 https://static.rust-…

初识算法 · 双指针(1)

目录 前言&#xff1a; 双指针算法 题目一&#xff1a; ​编辑 题目二: 前言&#xff1a; 本文作为算法部分的第一篇文章&#xff0c;自然是少不了简单叭叭两句&#xff0c;对于算法部分&#xff0c;多刷是少不了&#xff0c;我们刷题从暴力过度到算法解法&#xff0c;自…

csp-j模拟二补题报告

目录传送门 前言第一题下棋&#xff08;chess&#xff09;我的代码&#xff08;AC了&#xff09;AC代码 第二题汪洋&#xff08;BigWater&#xff09;我的代码&#xff08;0&#xff09;AC代码 第三题删数&#xff08;delnum&#xff09;我的代码&#xff08;0&#xff09;AC代…

秋招突击——9/13——携程提前准备和实际面经——专程飞过去线下,结果一面挂(难受)

文章目录 引言面经收集面经整理一1. ArrayList和LinkedList2. 线程安全的列表和链表有么&#xff1f;如果没有怎么实现&#xff1f;3. threadlocal4. synchronized锁升级过程及原理5. ReentrantLock原理&#xff0c;以及和synchronized的对比6. 线程池工作原理7. redis常用数据…