我们在完成android需求时,可能会遇到多种控件同时可以滑动的场景,这时就可能会出现滑动冲突。在学习完android事件的分发机制之后,我们来学习滑动冲突的处理办法。
基础
常见的滑动冲突场景
场景一:外部滑动方向和内部不一致。
场景二:外部滑动方向和内部一致。
场景三:以上两种情况的嵌套。
场景一的应用场景示例:ViewPager和Fragment的配合。(ViewPager内部处理了滑动冲突)
场景二的应用场景示例:SrollerView里嵌套ListView。
处理规则 场景1 因为内外两层的滑动方向不一致,所以当用户进行外层滑动时,需要让外部的View拦截点击事件,当用户进行内层的滑动时,需要让内部View拦截点击事件。这时我们可以根据滑动的特征来解决滑动冲突。即根据滑动的方向来判断拦截事件的View 。
这个问题就变得很简单了——我们可以根据滑动过程中两个点的坐标来得出滑动的方向 。
例如:
水平方向和竖直方向的距离差
滑动路径和水平方向形成的夹角
水平和竖直方向的速度差
场景2 这个场景无法根据场景一的判断条件来做出判断,但我们一般能在业务上找到突破点。比如在某种状态时需要外部View响应用户的滑动,在另一种状态时需要内部View响应。
解决方式 抛开滑动规则,我们需要找到一种不依赖具体的滑动规则的通用的解决方法。
外部拦截法 即点击事件都先经过父容器的拦截处理。如果父容器需要此事件就进行拦截,否则不拦截。
需要重写父容器的onInterceptTouchEvent方法 ,在方法内做相应的拦截。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override public boolean onInterceptTouchEvent (MotionEvent event) { boolean intercept = false ; int x = (int ) event.getX(); int y = (int ) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: intercept = false ; break ; case MotionEvent.ACTION_MOVE: if ( *父容器需要的当前点击事件* ){ intercept = true ; }else { intercept = false ; } break ; case MotionEvent.ACTION_UP: intercept = false ; break ; } lastInterceptX = x; lastInterceptY = y; return intercept; }
针对不同的滑动冲突,只需要修改父容器需要的当前点击事件这个条件即可。
1:不可拦截ACTION_DOWN事件,一旦拦截,后续MOVE和UP事件都会交由父容器处理
2:必须返回false.如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发。而父View不一样,如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理!
内部拦截法 父容器不拦截任何事件,所有事件都传递给子元素,根据子元素对事件的消耗情况判断是否交由父容器处理。
与Android中事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作
需要重写子元素的diapatchTouchEvent方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public boolean dispatchTouchEvent (MotionEvent event) { int x = (int ) event.getX(); int y = (int ) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true ); break ; case MotionEvent.ACTION_MOVE: int deltaX = x - lastX; int deltaY = y - lastY; if ( *父容器需要此类点击事件* ){ getParent().requestDisallowInterceptTouchEvent(false ); } break ; case MotionEvent.ACTION_UP: break ; lastX = x; lastY = y; return super .dispatchTouchEvent(event); }
父元素要默认拦截除了ACTION_DOWN以外的其他事件 ,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时(即子元素不对该事件进行处理,交由父容器处理),父元素才能继续拦截所需事件。
1 2 3 4 5 6 7 8 9 @Override public boolean onInterceptTouchEvent (MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { return false ; } else { return true ; } }
为什么父容器不能拦截ACTION_DOWN事件呢?
因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT这个标记位的控制 ,所以一旦父容器拦截了ACTION_DOWN事件,则所有事件都无法传递到子元素中去。
实例 1.场景一 外部拦截 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Override public boolean onInterceptTouchEvent (MotionEvent event) { boolean intercept = false ; int x = (int ) event.getX(); int y = (int ) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: intercept = false ; Log.d("this" ," false" ); if (!scroller.isFinished()) { scroller.abortAnimation(); } break ; case MotionEvent.ACTION_MOVE: int deltaX = x - lastInterceptX; int deltaY = y - lastInterceptY; if (Math.abs(deltaX) - Math.abs(deltaY) > 0 ){ intercept = true ; Log.d("this" ,"true" ); }else { intercept = false ; Log.d("this" ,"false" ); } break ; case MotionEvent.ACTION_UP: intercept = false ; break ; } lastX = x; lastY = y; lastInterceptX = x; lastInterceptY = y; return intercept; }
内部拦截 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override public boolean dispatchTouchEvent (MotionEvent event) { int x = (int ) event.getX(); int y = (int ) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mHorizontalView1.requestDisallowInterceptTouchEvent(true ); break ; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (Math.abs(deltaX) > Math.abs(deltaY)) { mHorizontalView1.requestDisallowInterceptTouchEvent(false ); } break ; } case MotionEvent.ACTION_UP: { break ; } default : break ; } mLastX = x; mLastY = y; return super .dispatchTouchEvent(event); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public boolean onInterceptTouchEvent (MotionEvent event) { int x = (int ) event.getX(); int y = (int ) event.getY(); if (event.getAction() == MotionEvent.ACTION_DOWN){ lastX = x; lastY = y; if (!scroller.isFinished()){ scroller.abortAnimation(); return true ; } return false ; }else { return true ; } }
由于内部拦截比较复杂,一般不推荐使用。
未完待续……