前言:滑动冲突是如何产生的呢?其实在界面中只要内外两层同时可以滑动,这个时候就会产生滑动冲突。如何解决滑动冲突呢?滑动冲突的解决有固定的套路,只要知道了这个固定套路问题就好解决了。
一:常见的滑动冲突场景
场景1:外部滑动方向和内部滑动方向不一致;
主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果。在这个效果中,可以通过左右滑动来切换页面,而每个页面内部往往又是一个ListView。本来这种情况下是有滑动冲突的,但是ViewPager内部处理了这种滑动冲突,因此采用ViewPager时我们无须关注这个问题,如果我们采用的不是ViewPager而是ScrollView等,那就必须手动处理滑动冲突了,否则造成的后果就是内外两层只能一层能够滑动,这是因为两者之间的滑动事件有冲突。除了这种典型情况外,还存在其他的情况,比如外部上下滑动,内部左右滑动等,但是它们属于同一类滑动冲突。
场景2:外部滑动方向和内部滑动方向一致;
场景3:上面两种情况的嵌套。
二:滑动冲突的处理规则
对于场景1,它的处理规则是:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件。这个时候我们就可以根据它们的特征来解决滑动冲突,具体来说是:根据滑动是水平滑动还是数值滑动来判断到底有谁来拦截事件,根据滑动过程中两点之间的坐标就可以得出到底是水平滑动还是竖直滑动。如何根据坐标来得到滑动的方向呢?可以依据滑动路径和水平方向所形成的夹角,也可以依据水平方向和竖直上的距离差来判断,某些特殊时候还可以依据水平和竖直方向的速度差来判断。
场景2,3都是从业务的需求上得出相应的处理规则。
三、滑动冲突的解决方式
针对滑动冲突,这里给出两种解决滑动冲突的方式:外部拦截法和内部拦截法。
1.外部拦截法
所谓外部拦截法是指点击事情都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。在onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交由父容器处理,这个时候事件没法再传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回false;最后是ACTION_UP事件,这里必须要返回false,因为ACTION_UP事件本身没有太多意义考虑一种情况,假设事件交由子元素处理,如果父容器在ACTION_UP时返回了true,会导致子元素无法接收到ACTION_UP事件,这个时候子元素中的onClick事件就无法触发,但是父容器比较特殊,一旦它开始拦截任何一个事件,那么后续的事件都会交给它处理,而ACTION_UP作为最后一个事件也必定可以传递给父容器,即便父容器的onInterceptTouchEvent方法在ACTION_UP时返回了false。
2.内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素要消耗此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。它的伪代码如下,我们需要重写子元素的dispatchTouchEvent方法:
上述代码就是内部拦截法的典型代码,当面对不同的滑动策略只需要修改里面的条件即可,其他不需要做改动,除了子元素需要处理之外,父元素默认也要拦截除ACTION_DOWN之外的其他事件,这样当子元素调用getParent().requestDisallowInterceptTouchEvent(true)方法时,父元素才能继续拦截所需要的事件。为什么父容器不能拦截ACTION_DOWN事件呢?那是因为ACTION_DOWN事件并不接受FLAG_DISALLOW_DOWN这个标记位的控制,所以一旦父容器拦截,那么所有的事件都无法传递到子元素中,这样额你不拦截就无法起作用了,父元素要做的如下修改。
因为内部拦截法没有外部拦截法简单易用,所以推荐采用外部拦截法来解决常见的滑动冲突。
参考链接:https://blog.csdn.net/qq_26787115/article/details/53047914