View的滑动冲突和解决方案
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
场景三建议使用内部拦截方法比较方便