即刻App详情页滑动效果,SlideLayout双列表页面实现

即刻 5.3 版本的时候,随着圈子详情页的内容越来越丰富,之前的页面结构已经不能满足我们的需求,需要一个新的布局方案承载各种圈子元素并满足我们的自定义交互。

改版前

改版前的结构比较简单,头部显示圈子的基本信息比如图片、标题和简介等信息,底部展示圈子内的消息列表,向上滑动可折叠头部区域让用户更加专注地浏览消息列表,结构如下:

CoordinatorLayout 作为容器负责两部分的布局和联动滑动,AppBarLayout 负责展示头部信息,底部通过 ViewPagerFragment 实现多 tab 页面,Fragment 内部通过 RecyclerView 实现消息列表。

改版后:

改版后头部新增了一些元素比如插件、创建者,原有的元素展示区域扩大,导致头部高度增大。使得用户刚进入圈子页时几乎看不到消息列表区域,为了解决这个问题我们需要页面支持快速地在头部和列表之间切换,并且当头部超过一屏时也可以滑动。简单总结下我们的需求:

1、当头部信息较少,即没有达到一屏时表现和原有实现一致,头部随列表滑动可以折叠。

2、当头部信息较多,即超过一屏时除了头部随着列表滑动折叠外,还可以在头部和列表之间快速切换。

解决方案

第一条需求原有的 CoordinatorLayout 就可以支持,问题是第二条中的快速切换如何实现,最终我们的产品同事给出的解决方案如下:

从这个截图看上去好像和原来的将头部折叠一样,其实不然。将头部折叠需要先将头部滑到界面外,而这里头部其实没有滑动,列表是盖在头部上面,当想查看头部时再将列表滑下去。

有人可能会说,这和原来的有什么区别,都需要滑动。这种实现的好处主要有三点:

  • 列表只会存在展开和隐藏两种状态,不会存在显示一半的情况,当将列表拖到屏幕中间松手时会自动滑动到展开或隐藏,降低头部和列表切换的难度。

  • 当列表滑动几屏后,此时仍然可以拖动 scroll bar 将列表滑出展示头部,不需要将列表滑到最顶部再拉出头部。

  • 当头部很长时,头部内容滑动在任何位置都可以拖动 scrollbar 滑出列表,不需要将头部滑到最底部。

这里的 scrollbar 是个辅助组件,后面会讲到,这里先不展开。原有的 CoordinatorLayout 不能满足上述需求,所以我们需要实现一个自定义组件,由于这个组件的主要功能就是将页面底部滑出滑进,所以我们将这个组件命名为SlideLayout

思路详述

嵌套滚动

不管是原有逻辑还是新增的,都属于一对嵌套组件的联动交互,不难看出需要用到嵌套滚动机制来实现。首先我们简单了解下嵌套滚动的机制:

图中 Parent 表示实现了 NestedScrollingParent 接口的组件,Child 表示实现了 NestedScrollingChild 接口的组件,Parent 接受 Child 分发的滚动事件,而且他们不直接关联。

SlideLayout 结构

如上图在 SlideLayout 中当下拉出刷新动画时可以看到三个组件:refresh、header 和 slider,它们的含义如下:

  • refresh:当用户下拉刷新页面时 refresh 负责展示加载动画。

  • header:负责页面头部,需要包含实现 NestedScrollingChild 的组件从而向 SlideLayout 分发滚动事件。

  • slider:负责列表区域,也需要包含实现 NestedScrollingChild的组件,原因同 header

实现 NestedScrollingChild 的组件有NestedScrollViewRecyclerView 等,就圈子详情页这个页面来说,NestedScrollView实现了页面头部,RecyclerView 实现了消息 列表。

操作状态

在处理 SlideLayout 中的滚动事件时,我们用一个枚举类型定义了三个状态:

enum class SlideGesture { SCROLL, SLIDE, REFRESH }
SCROLL

sliderheader 处于连接的状态,即 header 的底边连着 slider 的顶边没有重叠。此状态时需要和老版本保持一致,即头部随着列表的滚动而滚动。如下图:

SLIDE

slider 盖在 header 上面,slider 此时有可能展示也有可能隐藏,主要工作是将 slider 滑出或者隐藏。如下图:

REFRESH

展示刷新动画,即刷新动画部分高度大于0。下图只展示了从 Scroll 状态转换而来的情况,其实从 Slide 状态也可以进入 Refresh 状态,和这个类似会在头部上面出现一个刷新动画展示区域,这里就不列出了。

三个状态的彼此转换关系如下图:

确认了状态定义后剩下的工作基本就分为两部分:状态识别和滚动处理。

状态识别

根据前面讲的状态定义可得出状态判断逻辑如下:

我们将 Refresh 状态的优先级设为最高,先判断刷新区域的高度是否大于 0来检查是不是 Refresh 状态。由于 Slide 的定义是 sliderheader 有重叠,而 sliderSlideLayout 中是通过 sliderTop 来表示位置的,所以我们可以通过 sliderTop < headerHeight 来判断是不是 Slide 状态。最后两个条件都不满足的话就是 Scroll 状态了。

滚动处理

针对不同状态,对滚动事件定义了不同的处理规则,从而实现我们需要的交互效果。具体的处理逻辑见下表:

横向表示三种状态,竖向表示两种滚动事件类型,组合出六种不同的 case。这里给出的逻辑处理比较简单,实际实现时会遇到很多需要特殊处理的情况,这里就不一一列出了,感兴趣的同学可以查看项目源码,项目地址会在最后给出。

使用实例

我们通过动图来看看最终实现的效果,第一种是头部没超过屏幕的情况:

然后再看看头部超过屏幕的情况:

页面布局结构如下:

<SlideLayout>
    <!-- header -->
    <FrameLayout>
        <androidx.core.widget.NestedScrollView>
            <!-- header content here -->
        </androidx.core.widget.NestedScrollView>
    </FrameLayout>
    <!-- slider -->
    <FrameLayout>
        <LinearLayout>
            <SlideBarLayout>
                <!-- slide bar content here -->
            </SlideBarLayout>
            <androidx.recyclerview.widget.RecyclerView />
        </LinearLayout>
    </FrameLayout>
    <!-- refresh -->
    <RefreshViewLayout/>
</SlideLayout>
  • SlideBarLayout:上面 gif 图里的 scroll bar,参考 AppBarLayout实现的滑动条组件,我们开源的项目中有源码,感兴趣的同学可以前去查看。

  • RefreshViewLayout:用于存放刷新动画组件的容器,可以通过实现 RefreshView 接口创建自定义的刷新动画,并设置给RefreshViewLayoutrefreshInterface 来生效。

  • 更多的使用方式可以访问 SlideLayout 项目主页查看。

总结

本文介绍了为什么需要 SlideLayout,并简单阐述了设计思路和实现机制,希望给读者有所启发和帮助。作为嵌套滚动机制的一种具体实现,在开发过程中让我深切感受到这套接口功能的强大,定义虽然简单,但却几乎能实现各种页面联动效果。由于本人水平有限,文章或者代码如果有任何问题实属难免,欢迎评论指正或者提 issue。

SlideLayout 项目地址: https://github.com/iftechio/iftech-android-slide-layout ,

Android开发资料+面试架构资料 免费分享 点击链接 即可领取

《Android架构师必备学习资源免费领取(架构视频+面试专题文档+学习笔记)》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现移动端App中的页面放大并进行滑动查看,可以借助于一些第三方库和浏览器自带的一些属性来实现。下面是一个简单的示例代码: 1. 引入依赖: ```javascript import Hammer from 'hammerjs'; // 引入Hammer.js库 ``` 2. 在mounted钩子函数中初始化Hammer,并绑定事件: ```javascript <script> export default { mounted() { const el = this.$refs.zoom; const mc = new Hammer.Manager(el); const pinch = new Hammer.Pinch(); mc.add(pinch); let currentScale = 1; let lastScale = 1; let posX = 0; let posY = 0; let lastPosX = 0; let lastPosY = 0; mc.on('pinchstart', function(ev) { lastScale = currentScale; }); mc.on('pinchmove', function(ev) { currentScale = lastScale * ev.scale; el.style.transform = `translate3d(${posX}px, ${posY}px, 0) scale(${currentScale})`; }); mc.on('panmove', function(ev) { posX = lastPosX + ev.deltaX; posY = lastPosY + ev.deltaY; el.style.transform = `translate3d(${posX}px, ${posY}px, 0) scale(${currentScale})`; }); mc.on('panend', function(ev) { lastPosX = posX; lastPosY = posY; }); } } </script> ``` 这里使用了Hammer.js库来处理触摸事件。在mounted钩子函数中,我们初始化Hammer,并绑定pinch和pan事件。在pinch事件中,我们计算出当前的缩放比例,并将其应用到元素的transform属性上。在pan事件中,我们计算出元素的位置,并将其应用到元素的transform属性上。在panend事件中,我们记录下元素的最后位置,以便下一次操作时使用。 3. 在模板中添加元素,并绑定ref属性: ```html <template> <div ref="zoom"> <!-- 页面内容 --> </div> </template> ``` 这里使用一个div元素来包裹页面内容,并将其绑定到ref属性上,以便在mounted钩子函数中使用。 4. 在样式中设置元素的初始大小和位置: ```css .zoom { position: relative; overflow: hidden; width: 100%; height: 100%; transform-origin: 0 0; transition: transform 0.3s ease-out; } ``` 这里设置了元素的宽高为100%,通过设置transform-origin属性,使缩放时页面的中心点为左上角。同时设置了过渡效果,使缩放时有平滑的过渡效果。 通过以上步骤,我们就可以实现一个移动端App中的页面放大并进行滑动查看的效果
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值