这是我参与「第四届青训营 」笔记创作活动的第9天
滑动冲突
1、常见的滑动冲突场景
-
场景1:外部滑动方向和内部滑动方向不一致。
主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果。在这种效果中,可以通过左右滑动来切 换页面,而每个页面内部往往又是一个ListView。本来这种情况下是有滑动冲突的,但是ViewPager内部处理了这种滑动冲突,因此采用ViewPager时我们无须 关注这个问题,如果我们采用的不是ViewPager而是ScrollView等,那就必须手动处理滑动冲突了,否则造成的后果就是内外两层只能有一层能够滑动,这是 因为两者之间的滑动事件有冲突。
-
场景2:外部滑动方向和内部滑动方向一致。当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题。因为当手指开始滑动的时候,系统无法知道 用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层能滑动,要么就是内外两层都滑动得很卡顿
-
场景3:上面两种情况的嵌套。内层有一 个场景1中的滑动效果,然后外层又有一个场景2中的滑动效果。具体说就是,外部有一个SlideMenu效果,然后内部有一个ViewPager,ViewPager的每一个页 面中又是一个ListView。虽然说场景3的滑动冲突看起来更复杂,但是它是几个单一的滑动冲突的叠加,因此只需要分别处理内层和中层、中层和外层之间的 滑动冲突即可,而具体的处理方法其实是和场景1、场景2相同的
3、滑动冲突的解决方法:
-
外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截,符合事件分发机制,这种方法需要重写父容器的onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent event){ boolean intercepted=false; int x=(int) event.getX(); int y=(int) event.getY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN intercepted=false;//不拦截DOWN事件 case MotionEvent.ACTION_MOVE if(父容器需要当前点击事件){ intercepted=true//父容器需要处理点击事件就拦截 }else { intercepted=false; } break; } case MotionEvent.ACTION_UP:{ intercepted=false; break; } default: break; } mLastXIntercept=x; mLastYIntercept=y; return intercepted; } -
内部拦截法:
父容器不拦截任何事件,所有事件传递给子View,子View需要此事件就拦截消耗掉,否则就交给父容器处理,这种方法不同于Android的事件分发机制,需要配合requestDisallowInterceptTouchEvent()方法,这个方法是用来动态控制父元素是否拦截点击事件的,重写子元素的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event){ int x=(int) event.getX(); int y=(int) event.getY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN:{ parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE:{ int deltaX=x-mLastX; int deltaY=y-mlastY; if(父容器需要此点击事件){ //允许拦截 parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP:{ break; } default: break; } mLastX=x; mLastY=y; return super.dispatchTouchEvent(event); } }上面是子元素做的处理,除此之外,父元素也要做处理
父元素需要拦截除了ACTION_DOWN之外的其它事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件
为什么父容器不能拦截ACTION_DOWN事件呢?那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父 容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截就无法起作用了。
父元素代码如下
public boolean onInterceptTouchEvent(MotionEvent event){ int action=event.getAction(); if(action==MotionEvent.ACTION_DWON){ return false;//表示不拦截 }else{ return true; } }
外部拦截和内部拦截的时机不同,本质上解决问题的思想没有区别
内部拦截法没有外部拦截法简单易用,所以一般使用外部拦截法
2、滑动冲突的处理规则:
-
场景1:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事 件。这个时候我们就可以根据它们的特征来解决滑动冲突
根据坐标来确定滑动方向从而拦截点击事件:可以依 据滑动路径和水平方向所形成的夹角,也可以依据水平方向和竖直方向上的距离差来判断,某些特殊时候还可以依据水平和竖直方向的速度差来做判断。
-
场景2,3:需要根据业务需求来决定。比如业务上有规定: 当处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态时则需要内部View来响应View的滑动