Android 三级NestedScroll嵌套滚动实践

嵌套滚动介绍

我们知道 NestedScrolling(Parent/Child) 这对接口是用来实现嵌套滚动的,一般实现这对接口的 Parent 和 Child 没有直接嵌套,否则直接用 onInterceptTouchEvent() 和 onTouchEvent() 这对方法实现就可以了。能够越级嵌套滚动正是它的厉害之处。

嵌套滚动的接口有两对:NestedScrolling(Parent/Child) 和 NestedScrolling(Parent2/Child2) 后者相比前者对 fling 的处理更加细致。相比第一代 Child 简单地将 fling 抛给 Parent,第二代 Child 将 fling 转化为 scroll 后再分发给 Parent,为了和普通的 scroll 区分增加了一个参数 type, 当 type 是 ViewCompat.TYPE_TOUCH 时表示普通的 scroll,当是 ViewCompat.TYPE_NON_TOUCH 时表示由 fling 转化而来的 scroll。这样做的好处是当 Child 检测到一个 fling 时,它可以选择将这个 fling 引起的 scroll 一部分作用在 Parent 上一部分作用在自己身上,而不是只作用在 Parent 或者 Child 上。或许你会问 fling 为什么不能选择 Parent 和 Child 都作用,事实上你可以,但 fling 的话 Parent 没法告诉 Child 消费了多少,剩下多少,因为 fling 传递的值是速度,不像 scroll 是距离。所以通过 NestedScrolling(Parent2/Child2) 实现嵌套滚动时,当你触发了一个 fling 时,也可以做很顺滑连贯的交替滚动,而 1 就很难达到相同的效果。现在官方 View 的实现也是通过 NestedScrolling(Parent2/Child2),所以我们在实现自定义的嵌套滚动时尽量用 2。

上面简单介绍了 NestedScrolling 2 和 1 的区别以及为什么要使用2。现在我们来看看 NestedScrolling(Parent2/Child2) 的方法,1 就不看了,和 2 差不多。

public interface NestedScrollingChild2 {

 void setNestedScrollingEnabled(boolean enabled);

 boolean isNestedScrollingEnabled();

 boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type);

 void stopNestedScroll(@NestedScrollType int type);

 boolean hasNestedScrollingParent(@NestedScrollType int type);

 boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
   int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,
   @NestedScrollType int type);

	boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
   @Nullable int[] offsetInWindow, @NestedScrollType int type);
}
public interface NestedScrollingParent2 {

	boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes,
   @NestedScrollType int type);

	void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes,
   @NestedScrollType int type);

	void onStopNestedScroll(@NonNull View target, @NestedScrollType int type);

	void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
   int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type);

	void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
   @NestedScrollType int type);
}

从这两个接口的方法可以看出这些方法都是一一对应的,比如 startNestedScroll 和 onStartNestedScroll,stopNestedScroll 和 onStopNestedScroll 等。从这些方法的命名上也能看出来嵌套滚动的交互顺序是 Child 主动触发,Parent 被动接受,所以决定是否打开嵌套滚动的方法 setNestedScrollingEnabled 由 Child 实现,决定开始和结束的方法 startNestedScroll 和 stopNestedScroll 也由 Child 实现。

这里用一个图来表示嵌套滚动流程

整个过程大概分为两部分:绑定和滚动分发。绑定部分可以理解为 Child 向上遍历找 NestedScrollingParent2 的过程,找到后调用它的 onStartNestedScroll 方法,如果返回 true 则说明这个 Parent 想接收 nested scroll,Child 会紧接着调 onNestedScrollAccepted 方法表示同意 Parent 处理自己分发的 nested scroll,对应上图中的 1 2 3。滚动分发部分 Child 将自己的 scroll 分为三个阶段 before scroll after,before 和 after 分发给 parent 消费,scroll 阶段让自己消费,这三个阶段是按顺序进行的,换句话说如果前一步消耗完了 scroll,那后面的阶段就没有 scroll 可以消费。这样做的好处是让 Parent 可以在自己消费之前或者之后消费 scroll,如果 Parent 想在 Child 之前消费就在 onNestedPreScroll 方法里处理,否则就在 onNestedScroll 方法里,对应上图中的 4 5 步。上面介绍到的一些通用逻辑被封装在 NestedScrollingChildHelper 和 NestedScrollingParentHelper 中,在 NestedScrolling(Parent2/Child2) 的方法中可以调用 Helper 类中的同名方法,比如 NestedScrollingChild2.startNestedScroll 方法中实现了向上遍历寻找 NestedScrollingParent 的逻辑。

三级嵌套滚动

一个常见的嵌套滚动例子是 CoordinatorLayout/AppbarLayout - RecyclerView, 实现的效果是向上滑动列表时,会先将 AppbarLayout 向上滑动直到完全折叠,向下滑动至列表最顶部后会展开 AppbarLayout, 如下图:

这里实现 NestedScrollingParent2 的是 CoordinatorLayout/AppbarLayout, 实现 NestedScrollingChild2 的是 RecyclerView。对于这种两级嵌套滚动的需求使用 CoordinatorLayout 几乎都能实现,如果遇到特殊的业务需求基于 CoordinatorLayout 和 RecyclerView 的实现改改也能实现。

我这里遇到的需求是即刻首页的样式(可参考即刻5.4.2版本),除了要有 AppbarLayout 折叠效果之外还要在 AppbarLayout 顶部展示搜索框和刷新动画。这里的滑动逻辑是:

  • 向上滑动时,最先折叠刷新动画,向下滑动时最后展开刷新动画。
  • 向上滑动列表时先折叠 AppbarLayout,AppbarLayout 完全折叠后再折叠搜索框。
  • 向下滑动列表时在展开 AppbarLayout 之前先展开搜索框。
  • 列表没滑动到顶部时可以通过触发一定速度的向下 fling 来展开搜索框。

可以发现这里除了 CoordinatorLayout/AppbarLayout - RecyclerView 这对嵌套滚动的 Parent 和 Child 之外还多了搜索框和刷新动画,而这三者之间的滑动逻辑需要通过嵌套滚动实现,只是传统的两级嵌套滚动不能满足,所以需要实现三级嵌套滚动。

所谓三级嵌套滚动是在两级嵌套滚动之上再添加一个 Parent,这里为了表述方便将三级嵌套滚动的三级由上到下分别称为 Grand Parent Child。具体是由两对 NestedScrolling(Parent2/Child2) 接口实现,Grand 实现第一对接口的 Parent,Parent 实现第一对接口的 Child 和第二对接口的 Parent,Child 实现第二对接口的 Child。与两级嵌套滚动相比三级嵌套的 Grand 和 Child 和两级的 Parent 和 Child 区别不大,变化比较大的是三级的 Parent 既要实现两级的 Parent 接口又要实现 Child 接口,示意图如下:

在即刻首页这个例子里,CoordinatorLayout/AppbarLayout 属于三级嵌套的 Parent 实现了第二对接口的 NestedScrollingParent2,RecyclerView 属于 Child 实现了第二对接口的 NestedScrollingChild2。这里我们需要做的是实现第一对嵌套接口,新建一个自定义 Layout 实现 NestedScrollingParent2 接口作为三级嵌套的 Grand,负责搜索框和刷新动画的折叠和展开。再新建一个自定义 Layout 继承 CoordinatorLayout 实现 NestedScrollingChild2 接口,负责拦截列表分发上来的滚动事件或者处理 AppbarLayout 消费后剩下的滚动事件。

二级嵌套滚动可以理解为给 Parent 提供了拦截 Child 滚动事件和处理 Child 剩余滚动事件的能力,具体逻辑可参考本文最开始介绍嵌套滚动的部分。相应的三级嵌套滚动给 Grand 提供了拦截 Parent 和处理剩余滚动事件的能力,只是拦截和处理的时机多了一些,如下图:

二级嵌套滚动对滚动处理时机只有三个阶段:preScroll、scroll 和 afterScroll。而三级嵌套滚动的处理时机就多一些,有七个阶段:prePreScroll、preScroll、afterPreScroll、scroll、preAfterScroll、afterScroll 和 afterAfterScroll,可以看出相比二级嵌套多了 prePreScroll、afterPreScroll、preAfterScroll 和 afterAfterScroll 这四个阶段,多出的这几个阶段都是给 Grand 用的。到这里可以发现 NestedScrollingParent2 其实不能完全描述 Grand 的能力,确实最理想的方案应该是新建一对接口 NestedScrollingGrand2 和 NestedScrollingGrandChild2 来描述新增的四个对滚动事件的处理阶段,但考虑到我这里的例子 Grand 对 Parent 的处理没有那么精细化,所以还是通过复用 NestedScrolling(Parent2/Child2) 和一些附加方法来实现。以后如果实现了 NestedScrolling(Grand2/GrandChild2) 接口,也会及时更新。根据上图即刻首页滑动的实现思路就很简单了:

  • onPrePreScroll 中执行折叠刷新动画的逻辑,onAfterAfterScroll 中执行展开刷新动画的逻辑。
  • onPreScroll 中执行折叠 AppbarLayout 的逻辑,onAfterPreScroll 中执行搜索框折叠的逻辑。
  • onAfterScroll 中执行展开 AppbarLayout 的逻辑,onPreAfterScroll 中执行搜索框展开的逻辑。
  • 列表没滑到顶部根据 fling 展开搜索框的逻辑单独在 Parent 的 onNestedPreFling 里做,这条算是一个特殊处理。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • android嵌套滚动入门实践

    嵌套滚动是 Android OS 5.0之后,google 为我们提供的新特性.这种机制打破了我们对之前 Android 传统的事件处理的认知.从一定意义上可以理解为嵌套滚动是逆向的事件传递机制. 如上图所示,其原理就是这样.那么下边我们从代码的层面看一下实现. 代码中主要涉及到了四个类: NestedScrollingChild.NestedScrollingChildHelper.NestedScrollingParent.NestedScrollingParentHelper 先看Nest

  • Android嵌套滚动NestedScroll的实现了解一下

    其实嵌套滚动已经算一个比较常见的特效了,下面这个动图就是嵌套滚动的一个例子: 看到这个动效,大家可能都知道可以用CoordinatorLayout去实现.其实CoordinatorLayout是基于NestedScroll机制去实现的,而我们直接通过NestedScroll机制也能很方便的实现这个动效. 原理 NestedScroll的其实很简单. 一般的触摸消息的分发都是从外向内的,由外层的ViewGroup的dispatchTouchEvent方法调用到内层的View的dispatchTou

  • Android NestedScrolling嵌套滚动的示例代码

    一.什么是NestedScrolling? Android在Lollipop版本中引入了NestedScrolling--嵌套滚动机制.在Android的事件处理机制中,事件序列只能由父View和子View中的一个处理.在嵌套滚动机制中,子View处理事件前会将事件传给父View处理,两者协作配合处理事件. 在嵌套滚动机制中,父View需实现NestedScrollingParent接口,子View需要实现NestedScrollingChild接口.从Lollipop起View都已经实现了Ne

  • Android 三级NestedScroll嵌套滚动实践

    嵌套滚动介绍 我们知道 NestedScrolling(Parent/Child) 这对接口是用来实现嵌套滚动的,一般实现这对接口的 Parent 和 Child 没有直接嵌套,否则直接用 onInterceptTouchEvent() 和 onTouchEvent() 这对方法实现就可以了.能够越级嵌套滚动正是它的厉害之处. 嵌套滚动的接口有两对:NestedScrolling(Parent/Child) 和 NestedScrolling(Parent2/Child2) 后者相比前者对 fl

  • Android进阶NestedScroll嵌套滑动机制实现吸顶效果详解

    目录 引言 1 自定义滑动布局,实现吸顶效果 1.1 滑动容器实现 1.2 嵌套滑动机制完成交互优化 1.2.1 NestedScrollingParent接口和NestedScrollingChild接口 1.2.2 预滚动阶段实现 1.2.3 滚动阶段实现 1.2.4 滚动结束 引言 在上一篇文章Android进阶宝典 -- 事件冲突怎么解决?先从Android事件分发机制开始说起中,我们详细地介绍了Android事件分发机制,其实只要页面结构复杂,联动众多就会产生事件冲突,处理不得当就是b

  • Android嵌套滚动与协调滚动的实现方式汇总

    目录 Android的协调滚动的几种实现方式 一.CoordinatorLayout + Behavior 二.CoordinatorLayout + AppBarLayout 三.MotionLayout 总结 Android的协调滚动的几种实现方式 上一期,我们讲了嵌套滚动的实现方式,为什么有了嵌套滚动还需要协调滚动这种方式呢?(不细讲原理,本文只探讨实现的方式与步骤!) 那在一些细度化的操作中,如我们需要一些控件随着滚动布局做一些粒度比较小的动画.移动等操作,那么我们就需要监听滚动,然后改

  • Android嵌套滚动的传统方法与思路

    前言 Android 的嵌套滚动,实现比较方便 横着滚动,ViewPager2 竖着滚动,NestedScrollingParent 顶上,有一个头部视图 header, 中间,有一个菜单视图 menu, 下面的是,内容视图, 一个 ViewPager2,包含几个 Tab, Tab 里面是列表 RecyclerView 本文,主要参考  hongyangAndroid/Android-StickyNavLayout Java 实现 基于 LinearLayout ,添加 NestedScroll

  • Android嵌套滚动和协调滚动的多种实现方法

    目录 Android的嵌套滚动的几种实现方式 一.嵌套滚动 NestedScrollingParent/Child 二.嵌套滚动 NestedScrollView 三.嵌套滚动-自定义布局 总结 Android的嵌套滚动的几种实现方式 很多 Android 开发者虽然做了几年的开发,但是可能还是对滚动的几种方式不是很了解,本系列也不会涉及到底层滚动原理,只是探讨一下 Android 布局滚动的几种方式. 什么叫嵌套滚动?什么叫协调滚动? 只要是涉及到滚动那必然父容器和子容器,按照原理来说子容器先

  • Android ScrollView 下嵌套 ListView 或 GridView出现问题解决办法

    Android ScrollView 下嵌套 ListView 或 GridView出现问题解决办法 ScrollView 下嵌套 ListView 或 GridView 会发列表现数据只能显示一行.因为他们都是滚动结构,两个滚动条放到一起就会引起冲突. 解决此问题可以通过计算 ListView 高度或重写 ListView 的 onMeasure 方法来解决.下面介绍通过重写 onMeasure 方法来解决问题. 重写 onMeasure 方法如下: public class ScrollLi

  • Android 中ScrollView嵌套GridView,ListView的实例

    Android 中ScrollView嵌套GridView,ListView的实例 在Android开发中,经常有一些UI需要进行固定style的动态布局,然而由于现在的UI都喜欢把一个界面拉的很长,所以我们很多情况下需要使用ScrollView来嵌套列表控件来实现UI.这样就导致了很多不顺心的问题. 问题一:列表控件显示不完全 原因是嵌套情况下,ScrollView不能正确的计算列表控件的高度. 有两种解决方案 方案一 在适配器赋值完成后代码动态计算列表的高度.这里贴出ListView的计算代

随机推荐