《Android艺术开发探索》学习笔记之View的事件体系(滑动冲突)

本文介绍了常见的滑动冲突场景及其处理规则,包括外部与内部滑动方向不一致或一致的情况,并提出了外部拦截法和内部拦截法两种解决方案。
常见的滑动冲突场景

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

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

3)上面两种情况的嵌套:具体的说就是,外部有一个SlideMenu效果,然后内部有一个ViewPager,ViewPager的每一个页面中又是一个ListView。虽然这种场景比较复杂,但是它是几个单一的滑动冲突的叠加,因此需要分别处理内层和中层,中层和外层之间的滑动冲突即可,而具体的处理方法和场景1、场景2相同的。

滑动冲突的处理规则

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

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

场景3处理:场景3的滑动规则更加复杂,和场景2一样,只能从业务上找到突破点,从业务需求得出相应的处理规则。

滑动冲突的解决方法

1)外部拦截法:是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突问题,这种方法比较适合点击事件的分发机制。外部拦截法需要重写父容器的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;
    }
复制代码

针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其他均不需要做修改并且也不能修改。在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方法。 伪代码:

    @Override
    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);//不允许拦截
                break;
            }
            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);
    }
复制代码

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

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN){
            return false;
        }else{
            return true;
        }
    }
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值