美文网首页
Android View的事件体系(五) View的滑动冲突

Android View的事件体系(五) View的滑动冲突

作者: BlingBest | 来源:发表于2019-01-14 10:45 被阅读25次

    滑动冲突时如何产生的呢?在界面中只要内外两层同时可以滑动,这个时候就会产生滑动冲突.

    常见的滑动冲突场景

    1 外部滑动方向和内部滑动方向不一致

    2 外部滑动方向和内部滑动方向一致

    3 上面两种情况的嵌套

    场景1 : 主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果,在这种效果中,可以通过左右滑动来切换页面,而每个页面内部往往又是一个ListView,本来这种情况下是有滑动冲突的,但是ViewPager内部处理了这种滑动冲突,因此采用ViewPager时我们无须关注这个问题,如果我们采用的不是ViewPager而是ScrollView等,那就必须手动处理滑动冲突了,否则造成的后果就是内外两层只有一层能够滑动,这是因为两者之间的滑动事件有冲突,除了这种典型情况外,还存在其他情况,比如外部上下滑动,内部左右滑动等,但是它们属于同一类滑动冲突.

    场景2 : 当内外两层都是同一个方向可以滑动的时候,显然存在逻辑问题,因此当手指开始滑动的时候,系统无法知道用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题,那么只有一层能滑动,要么就是内外两层都滑动得很卡顿,在实际开发中,这种场景主要是指内外两层同时能上下滑动或者内外两层同时能左右滑动

    场景3 : 场景3 是场景1 和场景2 两种情况得嵌套,因此场景3 的滑动冲突看起来更加复杂了,比如在许多应用中会有这么一个效果:内层有一个场景1 中的滑动效果,然后外层又有一个场景2中的滑动效果,具体说就是,外部有一个SlideMenu效果,然后内部有一个ViewPager,ViewPager的每一个中又是一个ListView,虽然说场景3 的滑动冲突看起来更复杂,但是它是几个单一的滑动冲突的叠加,因此只需要分别处理内层和中层,中层和外层之间的滑动冲突即可,而具体处理的方法其实是和场景1 场景2 相同的

    从本质上来说,这三种滑动冲突场景的复杂度其实是相同的,因为它们的区别仅仅是滑动策略的不同,至于解决滑动冲突的方法,是通用的

    滑动冲突的处理规则

    一般来说,不管滑动冲突多么复杂,它都有既定的规则,根据这些规则我们就可与选择合适的方法去处理

    场景1 : 它的处理规则是:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件,这个时候我们就可以根据它们的特征来解决滑动冲突,具体来说是:根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截事件,根据滑动过程中两个点之间的坐标就可以得出到底是水平滑动还是竖直滑动,如何根据坐标来得到滑动的方向呢,这个很简单,有很多可以参考,比如可以依据滑动路径和水平方向所形成的夹角,也可以依据水平方向和竖直方向上的距离差来判断,某些特殊时刻还可以依据水平和竖直方向的速度差来做判断,这里我们可以通过水平和竖直方向的距离差来判断,比如竖直方向滑动的距离大就判断为竖直滑动,否则判断为水平滑动.根据这个规则就可以进行下一步的解决方法了

    场景2 :它无法根据滑动的角度,距离差以及速度差来做判断,但是这个时候一把都能在业务上找到突破点.比如业务上有规定:当处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态时则需要内部View来响应View的滑动,根据这种业务上的需求我们也能得出相应的处理规则,有了处理规则同样可以进行下一步处理,这种场景通过文字描述可能比较抽象

    场景3 它的滑动规则就更复杂了,和场景2 一样,它也无法直接根据滑动的角度,距离差以及速度差来做判断,同样只能从业务上找突破点,具体方法和场景2 一样,都是从业务的需求上得出相应的处理规则

    滑动冲突的解决方式

    首先我们要分析第一种滑动冲突场景,这也是最简单,最典型的一种滑动冲突,因为它的滑动规则比较简单,不管多复杂的滑动冲突,它们之间的区别仅仅是滑动规则不同而已,抛开滑动规则不说,我们找到一种不依赖具体的滑动规则的通用的解决方法,在这里,我们就根据场景1 的情况来得到通用的解决方案:

    我们可以根据滑动的距离差来进行判断,这个距离差就是所谓的滑动规则,如果用ViewPager去实现场景1 的效果,我们不需要手动处理滑动冲突,因为ViewPager以及帮我们做了,但是为了更好的演示滑动冲突的解决思想,没有采用ViewPager,其实在滑动过程中得到滑动的角度这个是相当简单的,但是到底要怎么做才能将点击事件交给合适的View去处理呢,针对滑动冲突,这里给出两种解决滑动冲突的方式:外部拦截和内部拦截

    外部拦截法

    所谓外部拦截是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制,外部拦截法需要重写父容器的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方法时返回了false

    内部拦截法

    内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android 的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截稍显复杂,我们需要重写子元素的dispatchTouchEvent方法:

    上述代码是内部拦截的典型代码,当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要做改动而且也不能有改动,除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件.

    为什么父容器不能拦截ACTION_DOWN事件呢,那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截就无法起作用了

    相关文章

      网友评论

          本文标题:Android View的事件体系(五) View的滑动冲突

          本文链接:https://www.haomeiwen.com/subject/wywqdqtx.html