Android View的滑动冲突解决方法

1.前言

上一篇写了View的事件分发机制,Android View的事件分发机制简单理解
有了一点事件分发的基本理解,再来看这个滑动冲突就能更好的接收这个解决思路。

2.常见的滑动冲突场景

常见的滑动冲突有以下三种:
场景1.2.3

  • 场景A:外部滑动与内部滑动不一致的滑动冲突;
  • 场景B:外部滑动与内部滑动一致的滑动冲突;
  • 场景C:场景AB的嵌套;

A .这个情况常见于ViewPager和Fragment中LisetView的使用,然而ViewPager和内部处理了这种冲突,所以我们在使用时感觉不到有冲突事件,若是把ViewPager换成ScrollView等,就需要处理冲突。同样的,外部上下,内部左右也属于这一类冲突。
B .内外两层同个方向的滑动出现的滑动冲突,这种场景可能出现在自定义View与ListView中,外部可以上下滑动,内部也可以上下滑动,这时候处理冲突的方法就需要根据滑动的逻辑去处理。
C .这个场景就是上面两种场景的嵌套,内层有一个场景A的滑动效果,外部又有一个左右滑动的效果,嵌套起来看起来更复杂,但是它是几个单一的滑动冲突的叠加,只需要分别处理内层和中层,中层和外层之间的滑动冲突即可。

3.滑动冲突的处理规则

对于场景A,它的处理规则:当用户左右滑动时,让外部的View拦截点击事件,当用户上下滑动时,让内部的View拦截点击事件。那怎么判断用互是左右滑动还是上下滑动呢?
有很多种方法,这里介绍一种依据水平方向和竖直方向上的距离差来判断的方法。利用水平偏移量和竖直方向的偏移量进行相减,用是否大于0来判断哪个偏移量大。
如图:
在这里插入图片描述
偏移量offsetX-offsetY>0,可判断为水平滑动,这时可以由外部拦截,让它来处理点击事件。
对于场景B,就不能使用这个方法判断滑动事件由谁处理,因为它是同个方向的滑动,这时需要根据业务逻辑来处理,比如业务规定:当处于某种状态时需要用外部来的View来处理滑动,而处于另一种状态时需要内部View来处理滑动,这时需要根据自身的业务逻辑来处理。

4.滑动冲突的解决方式

处理滑动冲突有经典的两种方法:外部拦截法和内部拦截法。

4.1外部拦截法

点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要此事件则不拦截。重写onInterceptTouchEvent,
伪代码:

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int)ev.getX();
        int y = (int)ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要当前点击事件){
                    intercepted =true;
                }else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }
}

上述伪代码是外部拦截法的典型逻辑,利用外部拦截法去解决冲突事件时,只需要更改上述括号内“父容器需要当前点击事件”的条件就可以了。
在onInterceptTouchEvent方法中

  • ACTION_DOWN事件:父容器必须返回false,返回true表示父容器要拦截这个事件,一旦父容器拦截了ACTION_DOWN事件,接下来的ACTION_MOVE和ACTION_UP事件都会直接交给父容器处理,无法传递个子元素,因此必须返回false。

  • ACTION_DOWN事件:根据拦截条件确定是否拦截,需要拦截则返回true,不需要拦截则返回false。

  • ACTION_UP事件:必须返回false。如果点击事件交给了子元素处理,而ACTION_UP返回true,即父容器拦截了这个事件,则子元素的onClik方法就无法触发。如果点击事件交给了父容器处理,即父容器在ACTION_DOWN中拦截了此事件,则后续的ACTION_UP事件也一定是交给父容器处理了,此时无论返回ture或者false都无法改变,因此ACTION_UP事件中需要返回false。

4.2内部拦截法

父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件则直接消耗掉,否则交给父容器处理。
伪代码:

 public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int)ev.getX();
        int y = (int)ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                parent.requestDisallowInterceptTouchEvent(true); //不拦截事件
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if(父容器需要此类点击事件){
                    parent.requestDisallowInterceptTouchEvent(false);  //拦截事件
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(ev);
    }

同外部拦截法用法一样,使用时只需改动“父容器需要此类点击事件”条件,其他代码不变。
requestDisallowInterceptTouchEvent:在View的事件分发机制中讲到,点击事件是从父view传递到子view中,每一层的view可以决定是否拦截并处理点击事件或者传递到下一层,如果子view不处理点击事件,则该事件会传递会父view,由父view去决定是否处理该点击事件。

在子view可以通过设置此方法去告诉父view不要拦截并处理点击事件,父view应该接受这个请求直到此次点击事件结束,设置false表示需要父View拦截并处理这个事件。

参考自:
《Android开发艺术探索》

猜你喜欢

转载自blog.csdn.net/qq_44830568/article/details/105520287