1.滑动冲突原因:
当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突。
2.常见的冲突场景:
场景1:
场景2:
场景3:
4.解决方法种类:
(1)外部拦截法:
针对场景1,我们可以发现外部和内部的滑动方向不一样也就是说只要判断当前dy和dx的大小,如果dy>dx,那么当前就是竖直滑动,否则就是水平滑动。明确了这个我就就可以根据当前的手势开始拦截了。从view的事件分发中我们了解点击事件的分发顺序是 通过父布局分发,如果父布局没有拦截,即onInterceptTouchEvent返回false,才会传递给子View。所以我们就可以利用onInterceptTouchEvent()这个方法来进行事件的拦截。
1 public boolean onInterceptTouchEvent(MotionEvent event) {
2 boolean intercepted = false;
3 int x = (int) event.getX();
4 int y = (int) event.getY();
5
6 switch (event.getAction()) {
7 case MotionEvent.ACTION_DOWN: {
8 intercepted = false;
9 break;
10 }
11 case MotionEvent.ACTION_MOVE: {
12 if(父容器拦截的规则){
13 intercepted=true;
14 }else{
15 intercepted=false;
16 }
17 break;
18 }
19 case MotionEvent.ACTION_UP: {
20 intercepted = false;
21 break;
22 }
23 default:
24 break;
25 }
26 mLastXIntercept=x;
27 mLastYIntercept=y;
28 return intercepted;
29 }
上面的代码差多就是外部拦截的通用模板了,在onInterceptTouchEvent方法中,
首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截事件,因为一旦父容器拦截了ACTION_DOWN这个事件,那么后续的ACTION_MOVE和ACTION_UP事件将直接交给父容器处理,这个时候事件没法继续传递给子元素了;
然后是ACTION_MOVE这个事件,这个事件可以根据需要决定是否拦截,如果父容器需要拦截就返回true,否则返回false;
最后是ACTION_UP这个事件,这里必须返回false,因为这个事件本身也没有太多意义。
so场景一的外部拦截解决方法:
1 public boolean onInterceptTouchEvent(MotionEvent event) {
2 boolean intercepted = false;
3 int x = (int) event.getX();
4 int y = (int) event.getY();
5
6 switch (event.getAction()) {
7 case MotionEvent.ACTION_DOWN: {
8 intercepted = false;
9 break;
10 }
11 case MotionEvent.ACTION_MOVE: {
12 int deltaX=x-mLastXIntercept;
13 int deltaY=y=mLastYIntercept;
14 if(Math.abs(deltaX)>Math.abs(deltaY)){
15 intercepted=true;
16 }else{
17 intercepted=false;
18 }
19 break;
20 }
21 case MotionEvent.ACTION_UP: {
22 intercepted = false;
23 break;
24 }
25 default:
26 break;
27 }
28 mLastXIntercept=x;
29 mLastYIntercept=y;
30 return intercepted;
31 }
场景二的外部拦截方法:
1 public boolean onInterceptTouchEvent(MotionEvent event) {
2 boolean intercepted = false;
3 int x = (int) event.getX();
4 int y = (int) event.getY();
5
6 switch (event.getAction()) {
7 case MotionEvent.ACTION_DOWN: {
8 mLastYIntercept=y;
9 intercepted = super.onInterceptTouchEvent(event);
10 break;
11 }
12 case MotionEvent.ACTION_MOVE: {
13 if(listView.getFirstVisiblePosition()==0 &&y>mLastYIntercept){
14 intercepted=true;
15 }else if(listView.getLastVisiblePosition()==listView.getCount-1&&y <mLastYIntercept){
16 intercepted=false;
17 break;
18 }
19 intercepted = false;
20 break;
21 }
22 case MotionEvent.ACTION_UP: {
23 intercepted = false;
24 break;
25 }
26 default:
27 break;
28 }
29 mLastXIntercept=x;
30 mLastYIntercept=y;
31 return intercepted;
32 }
(2)内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器去处理
(android系统中,一次点击事件是从父view传递到子view中,每一层的view可以决定是否拦截并处理点击事件或者传递到下一层,如果子view不处理点击事件,则该事件会传递会父view,由父view去决定是否处理该点击事件。在子view可以通过设置此方法去告诉父view不要拦截并处理点击事件,父view应该接受这个请求直到此次点击事件结束)需要用到方法requestDisallowInterceptTouchEvent。
内部拦截通用模板:
1 public boolean onInterceptTouchEvent(MotionEvent event) {
2 int x = (int) event.getX();
3 int y = (int) event.getY();
4
5 switch (event.getAction()) {
6 case MotionEvent.ACTION_DOWN: {
7 parent.requestDisallowInterceptTouchEvent(true); //父布局不要拦截此事件
8 break;
9 }
10 case MotionEvent.ACTION_MOVE: {
11 int deltaX=x-mLastXIntercept;
12 int deltaY=y=mLastYIntercept;
13 if(父容器需要拦截的事件){
14 parent.requestDisallowInterceptTouchEvent(false); //父布局需要要拦截此事件
15 }
16 break;
17 }
18 case MotionEvent.ACTION_UP: {
19 intercepted = false;
20 break;
21 }
22 default:
23 break;
24 }
25 mLastXIntercept=x;
26 mLastYIntercept=y;
27 return super.dispathTouchEvent(event);
28 }
so 场景二的内部拦截解决方法:
1 //拦截除了Down事件以外的其他方法:
2 public boolean onInterceptTouchEvent(MotionEvent event) {
3 int x = (int) event.getX();
4 int y = (int) event.getY();
5 int action = event.getAction();
6 if(action == MotionEvent.ACTION_DOWN){
7 mLastXIntercept=x;
8 mLastYIntercept=y;
9 return super.dispathTouchEvent(event);
10 }else{
11 return true;
12 }
13 }
14
15
场景三建议使用内部拦截方法比较方便