Android 滑动冲突的解决方法

一、常见的滑动冲突场景

场景1——外部滑动方向和内部滑动方向不一致,如:ViewPager中有多个fragment,而fragment中有ListView,这时ViewPager可以左右滑动,而ListView可以上下滑动,这就造成了滑动冲突。注意:这只是举个例子说明一下场景1,事实上ViewPager内部已经处理了这种滑动冲突,在采用ViewPager时,我们无需关注这个问题。

场景2——外部滑动方向和内部滑动方向一致。

场景3——上述两种场景的嵌套,即共有三层,外层与中层的滑动方向一致,而中层与内层的滑动方向不一致。

二、滑动冲突的处理规则

场景1的处理规则:
1、当用户左右滑动时,让外部的View拦截点击事件,当用户上下滑动时,让内部的View拦截点击事件;
2、判断用户的滑动方向(左右、上下):如果用户手指滑动的水平距离大于垂直距离,则左右滑动,反之,上下滑动;还可以根据角度、速度差来做判断;

场景2的处理规则:
无法根据滑动的角度、距离差、速度差来判断,因为场景2内部、外部的滑动方向一致;这时候一般都能在业务上找到突破点,如业务上规定:当处于某种状态需要外部View响应用户的滑动,而处于另一种状态时则需要内部View响应用户的滑动,所以我们可以根据业务的需求得出相应的处理规则。

场景3的处理规则:场景1的处理规则和场景2的处理规则一起用。

三、滑动冲突的解决方法

以下两种解决方法都是通用的方法:
1、外部拦截法
我们都知道点击事件是先经过父容器的拦截处理的,所以我们就可以在父容器的拦截方法中写下自己的逻辑代码即可,相当于我们要重写一个布局Layout,使其继承ViewGroup或其它的布局(LinearLayout等),然后重写其onInterceptTouchEvent方法。外部拦截法的伪代码如下:

 @Override
    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;
    }

解释上述代码:
1、父容器不拦截ACTION_DOWN事件:那是因为一旦父容器拦截了ACTION_DOWN事件,后续的ACTION_MOVE、ACTION_UP事件都会直接交给父容器处理,这个时候事件无法传递给子元素了。

2、父容器不拦截ACTION_UP事件:首先我们要知道onClick 事件是在ACTION_UP事件之后执行的,那当子元素有一个onClick事件,而这时候父容器拦截了ACTION_UP事件,那子元素的onClick事件就无法执行了。

3、ACTION_MOVE事件:在这里可以根据我们的需求,来判断父容器是否需要拦截事件,需要则返回true,否则返回false。

总结,外部拦截法比较简单,实现起来较容易。

2、内部拦截法
首先我们了解下ViewGroup.requestDisallowInterceptTouchEvent(boolean)方法,此方法就是在子View中通知父容器拦截或不拦截点击事件,false ——拦截,true ——不拦截;

内部拦截法的思想是父容器先不拦截任何事件,即所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这时我们就用到requestDisallowInterceptTouchEvent 方法了。

具体做法:我们重写子View的dispatchTouchEvent方法,即我们新建一个类继承子View,然后重写它的dispatchTouchEvent方法,伪代码如下:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //获得当前的位置坐标
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:

                //通知父容器不要拦截事件
                horizontalScrollLayout.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:

                if (父容器需要此事件){
                    //通知父容器拦截此事件
                    horizontalScrollLayout.requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }

        //重置手指的初始位置
        mLastY = y;
        mLastX = x;

        return super.dispatchTouchEvent(ev);
    }

解释上述代码:
1、horizontalScrollLayout是此子View的父容器的对象,我们可以在子 View中定义一个方法:

public void setHorizontalScrollLayout(HorizontalScrollLayout horizontalScrollLayout) {
        this.horizontalScrollLayout = horizontalScrollLayout;
    }

从而得到父容器的对象,然后就可以利用该对象通知父容器拦截或不拦截事件了。

2、我们还要重写父容器中的onInterceptTouchEvent方法,如下:

  @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }

此时,父容器拦截了除ACTION_DOWN以外的其它事件,那为什么这样做?
原因:只有这样,才能使的当子元素调用了requestDisallowInterceptTouchEvent(false)方法后,父容器才能继续拦截所需的事件。

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

总结,内部拦截法比较复杂,在实际应用中,建议还是用外部拦截法。

四、举例说明

在举例的时候,我自定义了几个view,大家主要看其中的dispatchTouchEvent方法和onInterceptTouchEvent方法,至于自定View的方法会在之后的博客中详细描述。

场景1的解决方案:外部拦截和内部拦截的方法都有 。代码下载请点我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值