首页 > 代码库 > 【滑动冲突】常见情形及解决方案

【滑动冲突】常见情形及解决方案

安卓开发过程中滑动冲突的情形主要有2类:
  • 父view与子view的滑动方向不同,如:父view左右滑动,子view上下滑动或相反(ViewPage里面嵌套ListView)。这种情形是比较简单的,只需要根据不同的滑动动作进行相应的拦截与处理即可。
  • 父view与子view的滑动方向相同,即,父view左右,子view也左右(ViewPage里面嵌套可以缩放、移动的ImageView)。这种情形需要根据具体情况来进行拦截处理,比如父View在出现子View滑动到边缘的情况才进行拦截处理

滑动冲突解决策略的理论基础为安卓的事件分发机制针对滑动冲突的解决策略有以下两种:
  • 一种是外部拦截法:即当事件满足滑动条件,通过父View的【onInterceptTouchEvent】方法对其进行拦截,拦截之后将直接进入父View的onTouchEvent进行事件消费,不会再传入下级view。
  • 二种是内部拦截法:即通过子View的【dispatchTouchEvent】方法接收到down事件,然后获取父View的requestDisallowInterceptTouchEvent方法禁止其onInterceptTouchEvent拦截,当满足父View滑动条件的时候才允许。这种方法需要父View不拦截down事件,因为拦截了down事件后所有子元素的触摸事件都会失效。
建议采用第一种方法,易于理解,不容易出错。


ViewPager里面嵌套ListView

实际测试后发现:原生根本就不会产生滑动冲突!
1、左右滑动(ViewPager处理触摸事件)
技术分享
  • 当ACTION_DOWN = 0,按下动作时,先是父ViewPager判断是否拦截,由于此时父ViewPager还不能判断是否要进行拦截,所以onInterceptTouchEvent返回false,即不拦截,所以ACTION_DOWN事件可以(必须、务必、一定)传到子ListView。
  • 当ACTION_MOVE = 2,移动动作时,父ViewPager尚未识别出此手势是左右滑动item的动作之前(一般是根据滑动的速度和累计距离判断)onInterceptTouchEvent同样也返回false,即同样不拦截,所最初的这些ACTION_MOVE事件同样也可以传到ListView。
  • 一旦父ViewPager识别出此手势是左右滑动item的动作(比如水平方向移动了足够长的距离)
    • 1、那么onInterceptTouchEvent将返回true(红色框位置),即开始拦截,所以之后的这些ACTION_MOVE事件将无法传到ListView;
    • 2、并且父ViewPager会在此时传给子View一个ACTION_CANCEL= 3的"取消动作",当子ListView收到此事件后便取消了之前对此手势的预处理;
    • 3、并且此后父ViewPager将不再调用onInterceptTouchEvent判断是否需要拦截(因为它认为此后的事件都是自己需要的)
  • 此后所有的ACTION_MOVE、ACTION_UP=1事件都交由父ViewPager的onTouchEvent处理了。

2、上下滑动(ListView处理触摸事件)
技术分享
可以发现,父ViewPager的onInterceptTouchEvent始终返回false,即从不会拦截触摸事件,所以上下滑动时的事件完全由ListView处理(这是最原始的状态)。

PS:
  • 父ViewPager的onInterceptTouchEvent在事件分发过程中(即dispatchTouchEvent方法调用时)并非是每次都会被调用的,一旦父ViewPager识别出此手势是自己需要的或不是自己需要的,之后都将不会再调用onInterceptTouchEvent判断是否需要拦截。
  • 所以判断判断某个View是否拦截了手势,只需看其最后一次调用onInterceptTouchEvent时的返回值即可。
  • 并且,很容易理解,其最后一次调用之前调用onInterceptTouchEvent时的返回值肯定都是false。

ListView里面嵌套ViewPager

1、上下滑动(ListView处理触摸事件)
技术分享
 情况和上面的一样
2、左右滑动(ViewPager处理触摸事件)
技术分享
情况同样和上面一样

ScrollView里面嵌套ListView

1、当ScrollView的内容没有超出屏幕时(也即ScrollView不需要上下滑动),不会产生滑动冲突(也即内部的ListView能正常滑动),所有触摸事件都由ListView处理。
 技术分享

2、当ScrollView的内容超出屏幕时(也即ScrollView需要上下滑动),由于ListView也需要上下滑动,所以会产生滑动冲突(也即内部的ListView讲不能正常滑动),所有触摸事件都由ScrollView处理
技术分享

SV嵌套LV滑动冲突 之 外部拦截法

外部拦截法即:重写父容器的onInterceptTouchEvent方法,当自己需要的时候就拦截,否则不拦截。可以很容易理解,因为这和android自身的事件处理机制逻辑是一模一样的。
技术分享
核心代码为:
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN://down事件肯定不能拦截,拦截了后面的就收不到了
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if (true) intercepted = false;//如果确定拦截了,就去自己的onTouchEvent里处理拦截之后的操作即可
            break;
        case MotionEvent.ACTION_UP:
            //up事件我们一般都是返回false的,一般父容器都不会拦截他, 因为up是事件的最后一步,这里返回true也没啥意义。
            //唯一的意义就是,如果父元素把up拦截了,将导致子元素收不到up事件,那子元素就肯定没有onClick事件触发了,这里的小细节 要想明白
            intercepted = false;
            break;
        default:
            break;
        }
        return intercepted;
    }

SV嵌套LV滑动冲突 之 内部拦截法

内部拦截法即:父容器不做处理,在子View中调用getParent().requestDisallowInterceptTouchEvent(true),作用是:告诉父view,我的触摸事件由我自行处理,不要阻碍我
不过前提是:要保证父亲容器不能拦截down事件。
技术分享
 核心代码为:
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            //这里指定在什么条件下,才会要求父View把触摸事件交给自己处理。注意这里是调用的【getParent().getParent()】
            if (true) getParent().getParent().requestDisallowInterceptTouchEvent(true);//这句话的作用是告诉父view,我的触摸事件由我自行处理,不要阻碍我
            break;
        case MotionEvent.ACTION_UP:
            getParent().getParent().requestDisallowInterceptTouchEvent(false);//个人感觉这行代码没啥用,因为父view本来就不会拦截
            break;
        }
        return super.dispatchTouchEvent(event);
    }

附件列表

     

    【滑动冲突】常见情形及解决方案