安卓NestedScrolling嵌套滑动机制基础

传统事件机制处理嵌套滑动的局限性

在传统的事件分发中,当一个事件产生后,它的传递过程遵循如下顺序:父控件->子控件,事件总是先传递给父控件,当父控件不对事件拦截的时候,那么当前时间又会传递给它的子控件。一旦父控件需要拦截事件,子控件是没有机会接受该事件的。

想要实现上图效果,在传统滑动机制中,我们需要以下几个步骤:

1 我们需要调用父控件中onInteceptTouchEvent方法来拦截向上滑动。

2 当父控件拦截事件后,需要控制自身的onTouchEvent处理滑动事件,使其滑动至HeaderView隐藏。

3 当HeaderView滑动至隐藏后,父控件就不拦截事件了,而是交给内部的子控件(RecyclerView或ListView处理)滑动事件。

使用传统的事件拦截机制处理嵌套滑动,我们会发现一个问题,就是整个嵌套滑动是不连贯的,也就是当父控件滑动至HeaderView隐藏的时候,这个时候如果想要内部的(RecyclerView或ListView)处理滑动事件,只有抬起手指,重新向上滑动。

熟悉事件分发机制的朋友应该知道,之所以产生不连贯的原因,是因为父控件拦截了事件,所以同一事件序列的事件,仍然会传递给父控件,也就会调用其onTouchEvent方法。而不是调用子控件的onTouchEvent方法。






在此文章开始之前,我想抛出一个问题:如何解决滑动冲突?

用传统的思路解决,你可能会从View的onInterceptTouchEvent和onTouchEvent方法入手,根据业务的情况以及手指滑动的方向,按需拦截事件来解决视图之间的滑动冲突。这种思路没有错,可以解决试图之间的冲突。

但这种思路有个局限,它无法解决嵌套滑动问题。

目前绝大多数滚动组件(RecyclerView,ScrollView,ListView等),我们翻看他的源码,都可以看到它们在处理move事件时有这样一段代码:

requestDisallowInteceptTouchEvent(true);

这段代码会禁止父控件拦截move,up事件。意味着一旦滚动组件的内胎被拖动了,事件就被滚动组件接管了,

父控件无法再通过onInteceptTouchEvent拦截同一事件序列中剩余的事件,因此更不会走到onTouchEvent中,处理自己滚动的逻辑了。

因此父控件只能等待下一个事件序列的到来,才能调用onInteceptTouchEvent事件让自己滚动,所有用onInteceptTouchEvent和onTouchEvent实现的嵌套滑动不够连贯,需要进行两次滑动操作。



在传统的触摸事件分发中,如果不手动调用分发事件或者分发事件出去,外部View最先拿到触摸事件,一旦它被外部View拦截消费了,内部View无法接收到触摸事件,同理的,内部View消费了触摸事件,外部View也没机会响应触摸事件。



NestedScrolling机制原理

NestedScrollingChild(简称nc),NestedScrollingParent(简称np)。逻辑上分别对应之前内部View和外部View的角色,下面引用一张NestedScrolling的交互过程。

接下来详细说明一下上图的交互流程:

1 当nc接收到触摸事件DOWN事件时,它会往外层布局遍历寻找最近的np请求配合处理滑动,所以它们层级不一定是直接上下级关系。

2 如果np不配合nc处理滑动那就没有接下来的流程,否则就会配合处理滑动。

3 nc要滑动之前,它会先拿到MOVE事件滑动的dx,dy,并将一个有两个元素的数组(分别表示np要滑动的水平和垂直方向距离)并作为参数一同传给np。

4 np拿到nc传来的数据,将要消费的水平和垂直的距离传进数组,这样nc就知道np要消费滑动值是多少了。

5 nc将dx,dy减去np消费滑动值,计算剩余的滑动值,如果剩余为0,说明np全部消费了,那么np不应该进行滑动,否则nc根据剩余的滑动值进行消费,然后将自己消费多少,还剩余多少汇报传递给np。

你可能感兴趣的:(安卓NestedScrolling嵌套滑动机制基础)