//今天来学习一下View的滑动冲突,主要从滑动冲突的产生原因和滑动冲突的解决方案出发。
View滑动冲突产生的三个冲突场景。
场景1、外部View左右滑动,内部View上下滑动
场景2、外部View上下滑动,内部View也上下滑动
场景3、场景1和场景2之间的混合滑动。
如图从左向右场景1-3
场景1和场景2都是比较常见的页面布局。因此解决这种滑动冲突十分必要
据我现在的了解,场景1现在可以使用ViewPager+Fragment来避免这种滑动的冲突。
场景2这种布局,我现在在写的项目中使用了NestedScrollView+RecyclerView这种布局方式,
也没有遇到这种冲突的情况。
所以,我想说的是,如果可以避免这些冲突那就尽量的去绕开这些冲突;如果避免不了滑动冲突,
那么我们可以借鉴以下的解决滑动冲突的思路,来解决我们的问题。
-----------------------------------------------------------------场景1---------------------------------------------------
首先来构造一个场景1的滑动冲突,并给出解决思路(这些例子来自于Android开发艺术探索)。
1 场景1的滑动冲突处理规则
场景1是一个简单的滑动冲突,外部View是左右滑动,内部View上下滑动。
当用户上下滑动时,我们希望内部View来处理事件;当用户左右滑动时,我们希望外部View来处理事件。
因此,我们可以通过判断用户的手势滑动方向,来决定是哪个View来处理事件。
1.1外部拦截法
1.事件拦截与处理
/**
* 事件拦截
*
* 通过手势的滑动,判断是上下滑动还是左右滑动。
* 如果是左右滑动,则父View进行事件的处理。
* 反之子View进行处理。
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
if(!mScroller.isFinished()){
mScroller.abortAnimation();
intercept = true;
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if(Math.abs(deltaX) > deltaY){ //左右滑动,父容器拦截事件
intercept = true;
}else{
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:break;
}
Log.i(TAG,"intercept:"+intercept);
mLastYIntercept = y;
mLastXIntercept = x;
Log.i(TAG,"x="+x+" y="+y+" mLastXIntercept="+mLastXIntercept+" mLastYIntercept="+mLastYIntercept);
return intercept;
}
/**
* 事件处理
*
* * @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(!mScroller.isFinished())
mScroller.abortAnimation();
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
scrollBy(-deltaX,0);
break;
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
mVelocityTracker.computeCurrentVelocity(1000);
//计算滑动速率
float xVelocity = mVelocityTracker.getXVelocity();
if(Math.abs(xVelocity) >=50){//滑动速率大于50.
mChildIndex = xVelocity > 0 ? mChildIndex-1 : mChildIndex+1;
}else{
mChildIndex = (scrollX + mChildWidth /2) / mChildWidth;
}
mChildIndex = Math.max(0,Math.min(mChildIndex,mChildSize - 1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx,0);
mVelocityTracker.clear();
break;
default:break;
}
mLastY = y;
mLastX = x;
return true;
}
1.2 内部拦截法:
重写子View的方法,并重写dispatchTouchEvent()方法,使用requestdisallowInterceptTouchEvent()方法通知父类是否拦截点击事件。
具体代码实现如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG," ----------------------mLastX="+mLastY+"---------------mLastY="+mLastY);
//告诉父View,不直接调用父View的onInterceptTouchEvent()方法,直接将方法分发给子View
horizontalScrollViewEX2.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.i(TAG," ----------------------mLastX="+mLastY+"---------------mLastY="+mLastY);
if(Math.abs(deltaX) > Math.abs(deltaY)){ //左右滑动的话,就转发给父View来处理
horizontalScrollViewEX2.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
Log.i(TAG," ----------------------MotionEvent...ACTION_UP");
break;
default:break;
}
mLastX = x;
mLastY = y;
Log.i(TAG," mLastX="+mLastY+"mLastY="+mLastY);
return super.dispatchTouchEvent(ev);
}
这里遇到过一个坑,就是requestDisallowInterceptTouchEvent(ture)失效的问题。查阅资料说,传入true时,父View不会调用onInterceptTouchEvent()方法去拦截事件。但是
我在子View中的ACTION_DOWN中设置了requestDisallowInterceptTouchEvent(ture),父View还是调用了onInterceptTouchEvent()方法,导致事件没有分发给子View。
我的解决方案是,在子View创建完成之后,再去创建父View对象。这样使得子View的dispatchTouchEvent()中的逻辑判断在父View之前执行。
该Demo的源码在我的github