滑动冲突常见情形及解决方案
Posted 白乾涛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滑动冲突常见情形及解决方案相关的知识,希望对你有一定的参考价值。
安卓开发过程中滑动冲突的情形主要有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滑动冲突 之 外部拦截法
核心代码为:
@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);
}附件列表
以上是关于滑动冲突常见情形及解决方案的主要内容,如果未能解决你的问题,请参考以下文章
Android NestedScrolling解决滑动冲突问题 - 项目实战
Android NestedScrolling解决滑动冲突问题 - 项目实战
从源码角度分析Android 事件分发机制以及常见滑动冲突解决方案