Android滑动冲突解决方案

本文是对任主席《android开发艺术探索》的滑动冲突知识学习和总结!
View体系中的滑动冲突是一个很深入的话题,这里只是简单介绍滑动冲突如何产生,常用的解决办法有哪些?

node1 滑动冲突是如何产生的?
在android中当布局嵌套时,如果父View和子View同时都可以滑动,往往就会造成滑动事件的冲突。解决滑动冲突是一件即困难又简单的事情,前提需要对事件分发有一定的了解,加上解决滑动冲突基本的套路,问题往往会迎刃而解!深情留不住,套路得人心...

node2 常见的滑动冲突的场景!


Android滑动冲突解决方案_第1张图片
滑动冲突场景.png

场景1 —— 外部滑动方向和内部滑动方向不一致
场景2 —— 外部滑动方向和内部滑动方向一致
场景3 —— 场景1和场景2的嵌套使用

上述几种滑动冲突的复杂程度其实是相同的,他们的区别仅仅是滑动策略的不同,解决滑动冲突的办法是一样的!一般来说不管冲突多么复杂,它都有自己处理的既定规则,按照这些规则,我们选择合适的处理方式,问题就很容易解决了。
场景1的处理规则:当用户左右滑动时,需要外部的父View不进行拦截,点击事件由自己处理,当用户上下滑动时,由父View处理滑动事件,子view则不进行滑动事件的响应。
场景2的处理规则:这个比较特殊,它无法根据滑动的角度,距离差以及熟读差来做判断,往往是根据业务逻辑来进行处理。比如:当处于某种状态时需要外部View滑动,而处于其他状态时,内部view需要滑动,总之有了这些条件,我们就可以对滑动进行相应的处理。
场景3就是场景1和场景2的复合场景,处理规则保持不变。

总结:上述3种场景处理规则就是当满足某种条件时父View响应滑动,满足另外的条件时子View响应滑动事件,所以他们的解决方式可以是一样的,只要适当的修改判断条件即可。。。

node3 滑动冲突的解决方式(通用)
3.1 外部拦截法
所谓的外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这种处理方式也是符合事件分发机制的流程的。外部拦截法需要重写父View的onInterceptTouchEvent()方法在内部做相应的处理即可:

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

        default:
            break;
        }
        mLastX = x;
        mLastY = y;
        return isIntercepted;
    }

上述代码是外部拦截的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其他的不需要修改,也不能修改。并且, MotionEvent.ACTION_DOWN事件必须返回false,一旦拦截,那么子View将无法接受所有的点击事件。 MotionEvent.ACTION_UP:一般不做处理,返回false即可,拦截也没有多大意义!

3.2 内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要就直接消费掉,否则交给父容器进行处理。这种方式和事件分发流程不太一致,需要父容器的onInterceptTouchEvent方法做相应处理,才能完成!使用起来比外部拦截法稍显复杂,我们需要重写子元素的dispatchTouchEvent方法:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            int dx = x - mLastX;
            int dy = y - mLastY;
            if(父View需要当前点击事件){
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            
            break;
        default:
            break;
        }
        
        mLastX = x;
        mLastY = y;
        
        return super.dispatchTouchEvent(ev);
    }

上述代码是内部拦截法的典型代码,使用时只需要修改对应的条件即可。其他的不需要修改,并且不能修改。除了子元素做相应的处理,父元素也要默认拦截除了action_down以外的其他事件,这样当子元素调用getParent().requestDisallowInterceptTouchEvent(false);时,父元素才能继续拦截所需要的事件。

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

你可能感兴趣的:(Android滑动冲突解决方案)