在开发一个UI的时候,遇到这么一个需求:
有一个列表,列表中N各item;列表可以上下滑动,item可以左右滑动;当列表上下滑动时,及时左右滑动,item也不可以滑动;同理,当item左右滑动时,列表也不可以上下滑动。
在动手之前,现捋捋android事件的分发流程。在android的事件分发传递中,有dispathTouchEvent()
、onInterceptTouchEvent()
和onTouchEvent()
三个方法:
dispathTouchEvent()
负责事件的分发,ViewGroup
和View
中都有这个方法。onInterceptTouchEvent()
负责事件的拦截,只有ViewGroup
中有这个方法,默认返回false
。onTouchEvent()
负责处理事件,ViewGroup
和View
中都有该方法,当返回false时,说明不消耗事件,会返回到上层的onTouchEvent()
;当返回true
时,会消耗改事件,后续的事件都会传递到该onTouchEvent()
方法,而不会传递给中间ViewGroup
的onTouchEvent
()方法。
具体的事件分发机制这里就不说了,网上有一大堆分析事件分发机制的文章,有兴趣的可以自行搜索,这里只大概说下事件的分发流程:
当屏幕接收到一个触摸事件的时候,会首先传递到
ViewGroup
的
dispatchTouchEvent()
,dispatchTouchEvent()
会调用onInterceptTouchEvent()
判断是否拦截该事件;当
onInterceptTouchEvent()
返回false
表示不拦截该事件,dispatchTouchEvent()
会遍历子View
,并根据触摸位置判断触摸的是哪个子View
,然后调用该子View
的dispatchTouchEvent()
方法,将事件传递下去;- 如果
onInterceptTouchEvent()
返回true
,表示拦截该事件,dispatchTouchEvent()
就会调用onTouchEvent()
,而不会将事件继续传递到子View
去。
当子
View
的ACTION_DOWN
事件返回了true
后,后续事件中,如果onInterceptTouchEvent()
返回true
,拦截事件,则子View
会立即接收到ACTION_CANCEL
的事件。
如果事件传递到最低层的View
后,还是返回false
,则后续的一系列事件都不会再传递下来。
了解了事件的传递流程后,我们就能对上面的需求进行详细分析了:
- 因为父
View
和子View
都会处理Touch
事件,所以在ACTION_DOWN
时需要在最底层的View
的onTouchEvent()
返回true
,表示接收处理事件。否则后续的事件都不会再传递进来,也就无法处理滑动了。 - 父
View
上下滑动,子View
左右滑动,所以需要在ACTION_MOVE
时,判断滑动方向。如果是上下滑动,则onInterceptTouchEvent()
返回true
,拦截该事件,事件交由自己的onTouchEvent()
去处理并返回true
;如果是左右滑动,则返回false
,将事件交由子View
处理。
判断滑动方向可以根据上下滑动速度和左右滑动速度比较得知。计算滑动速度需要借助
VelocityTracker
,VelocityTracker
的具体用法可以自己进行搜索
核心拦截代码如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, TAG + " ------> onInterceptTouchEvent:" + ev.getAction());
initVolocityTracker();
mVelocityTracker.addMovement(ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mStatus = STATUS_RESET;
mVelocityTracker.clear();
mVelocityTracker.addMovement(ev);
y = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.computeCurrentVelocity(1000);
float x = mVelocityTracker.getXVelocity(); //左右速度
float y = mVelocityTracker.getYVelocity(); //上下速度
//如果上下的速度大于左右的速度,则说明是上下滑动,需要拦截该事件
if (mStatus == STATUS_RESET && Math.abs(y) > Math.abs(x)) {
mStatus = STATUS_INTERCEPT;
return true;
} else if (x != 0 && y != 0){
mStatus = STATUS_UNINTERCEPT;
}
break;
}
return false;
}
demo:TouchEventDemo