Android滑动事件冲突解决方法

1.Android滑动事件冲突解决办法

滑动事件的冲突包括两种情形:

  • 不同方向的滑动冲突:比如ScrollView内部嵌套banner显示的viewPager
  • 同一方向事件滑动冲突:比如ScrollView内部嵌套ListView,ListView下拉刷新功能,需要ListView自身滑动实现滑动,但是当滑动到头部时需要ListView和Header一起滑动,也就是整个父容器的滑动,这时会产生滑动冲突.
2.不同方向滑动冲突的解决方案

一般根据在横坐标上滑动的距离与纵坐标滑动的距离相比较,如果横坐标上view滑动的距离大于纵坐标上view滑动的距离,则父view不进行事件拦截,否则父view进行事件拦截自己消费滑动事件,实现方式如下:

2.1外部拦截法:重写父view的拦截事件,由父view决定是否要拦截事件:
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int y = (int) e.getRawY();
        int x = (int) e.getRawX();
        boolean resume = false;
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 发生down事件时,记录y坐标
                mLastMotionY = y;
                mLastMotionX = x;
                resume = false;
                break;
            case MotionEvent.ACTION_MOVE:
                // deltaY > 0 是向下运动,< 0是向上运动
                int deltaY = y - mLastMotionY;
                int deleaX = x - mLastMotionX;

                if (Math.abs(deleaX) > Math.abs(deltaY)) {
                    resume = false;
                } else {
                         resume = true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return resume;
    }
2.2 内部拦截法:在view的onTouch方法中每次进入就设定父View不拦截此次事件,然后在MOTION_MOVE时候,根据滑动的距离判断再决定是父View是否有权利拦截Touch事件(即滑动行为)。
parentView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                view.getParent().requestDisallowInterceptTouchEvent(false);
                return false;
            }
        });

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                view.getParent().requestDisallowInterceptTouchEvent(true);
                int x = (int) event.getRawX();
                int y = (int) event.getRawY();

                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = x;
                        lastY = y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int deltaY = y - lastY;
                        int deltaX = x - lastX;
                        if (Math.abs(deltaX) < Math.abs(deltaY)) {
                         view.getParent().requestDisallowInterceptTouchEvent(false);
                        } else {
                            view.getParent().requestDisallowInterceptTouchEvent(true);
                        }
                    default:
                        break;
                }
                return false;
            }
        });
3.相同方向滑动冲突的解决方案

对于这种情况,比较特殊,我们没有通用的规则,得根据业务逻辑来得出相应的处理规则。举个最常见的例子,ListView下拉刷新功能,需要ListView自身滑动实现滑动,但是当滑动到头部时需要ListView和Header一起滑动,也就是整个父容器的滑动,这就涉及到滑动冲突问题了,如果不处理好滑动冲突,就会出现各种意想不到情况。对于这种情况的解决,我们可以采用拦截法:

3.1.外部拦截法(由父容器决定事件的传递):让事件都经过父容器的拦截处理(onInterceptTouchEvent ),如果父容器需要则拦截,如果不需要则不拦截,称为外部拦截法,其伪代码如下:
外部拦截法

1.首先down事件父容器必须返回false ,因为若是返回true,也就是拦截了down事件,那么后续的move和up事件就都会传递给父容器(onTouchEvent),子元素就没有机会处理事件了。
2.其次是up事件也返回了false,一是因为up事件对父容器没什么意义,其次是因为若事件是子元素处理的,却没有收到up事件会让子元素的onClick事件无法触发。

3.2内部拦截法(自己决定事件的传递):父容器不拦截任何事件,将所有事件传递给子元素,如果子元素需要则消耗掉,如果不需要则通过requestDisallowInterceptTouchEvent方法(请求父类不要拦截,返回值为true时不拦截,返回值为false时为拦截)交给父容器处理,称为内部拦截法,使用起来稍显麻烦,伪代码如下:

首先我们需要重写子元素的dispatchTouchEvent方法:


内部拦截法

然后修改父容器的onInterceptTouchEvent方法:


父容器拦截
//同一方向上的外部拦截法,ScrollView包括listView
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev!=null && containerArea(ev)){//如果子view listView在区域内说明子View需要消费该事件,
            // 父View ScrolloView不拦截事件,交给子View listView去执行
            return false;
        }
        return true;
    }

    public void setListView(ListView listView){
        this.listView=listView;
    }

    //判断是否在区域内
    private boolean containerArea(MotionEvent event){
        float eventX = event.getRawX();
        float eventY= event.getRawY();
        int [] outLocation =new int[2];
        listView.getLocationOnScreen(outLocation);
        float l = outLocation[0];
        float t = outLocation[1];
        float r = l+listView.getWidth();
        float b = t +listView.getHeight();
        if(eventX>=l&&eventX<=r&&eventY>=t&&eventY<=b){
            return true;
        }
        return false;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容