事件分发机制
Android的事件方法遵循一定的流程,即方法,拦截,消费.分别对应于以下方法:
dispatchTouchEvent: 分发事件
onInterceptTouchEvent:拦截事件
onTouchEvent:消费事件
能够响应这些方法的有ViewGroup,View,Activity.
Touch流程类别
Touch事件流程有两种一种是down,另外是move,up,这两种事件的分发机制有一定的区别.
Touch事件为什么有两种?我们可以看一下源码.
一.看ViewGroup对事件的分类处理
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
..........
..........
//看这段代码, DOWN事件时,满足一定的条件才会走拦截 onInterceptTouchEvent
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
..........................
//经过一系列的逻辑判断,走dispatchTransformedTouchEvent
.....................
dispatchTransformedTouchEvent {
............
//最后走到孩子的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
}
上面 代码在mGroupFlags & FLAG_DISALLOW_INTERCEPT为false才会走拦截事件,如果为true就会走孩子的diapatchTouchEvent,那我们”ctrl+f”看mGroupFlags & FLAG_DISALLOW_INTERCEPT什么时候会被赋值:
只在一个地方找到
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
上面的代码大概就是disallowIntercept为true就会修改标记值,请求父容器不要拦截,所以move事件会首先走到孩子的diapatchTiuchevent.
看Activity的处理
Activity实现了Window.Callback借口,复写dispatchTouchEvent,
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 对down事件做单独的处理
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
从上面的代码看到,Activity的dispatchTouchEvent对down事件做了处理,通过调用onUserInteraction(),将down事件交给自己来处理.move和up交给onTouchEvent来消费(Activity不存在View的嵌套,就不会有拦截方法了).
上面的源码总算的Touch事件的分类说清楚了,下面我们就讲一讲这两类事件的流程.
move,up事件
1.dispatchTouchEvent: 分发事件
1.1--true不分发
触摸事件结束
1.2--false分发
----2.onInterceptTouchEvent
2.2----true拦截
----3.onTouchEvent:消费事件
3.1----true自己消费,走自己的触摸逻辑
3.2----false自己不消费,触摸结束
2.2----false不拦截,事件来到子View
----diapatchTv
----true不分发.走3
----false分发,走自己的onInterceptTouchEvent..
总结:
move,up事件总是遵循上面的流程,对于嵌套的View,父View总是最先接收到触摸事件,子View最先响应事件(因为dispatchTouchEvent,onInterceptTouchEvent默认返回false).
down事件
对于down事件,流程与move,up有点不一样.google对down事件做了特别的处理,事件总是会先走到子View的diapatchTouchEvent.流程如下:
1.dispatchTouchEvent: 分发事件
if(满足一定的条件) {
走子View的dispatchTouchEvent...
}else{
正常的move,up...
}
满足的条件即子View对父容器的要求,即通过调用requestDisallowInterceptTouchEvent,设置几个标记,让父容器走拦截还是将down事件传给子View.
总结:
对于down事件,子VIew可以通过requestDisallowInterceptTouchEvent来获取事件的处理.google通过提供这个接口,防止父容器把事件给拦截了,让子View在需要时不能处理这个事件.
滑动冲突
在多层嵌套的view当中(如ViewPager与ViewPager,ScroolView与ViewPager……),经常会出现滑动的冲突,主要对Touch事件的流程比较清楚.在什么时候让父容器响应,什么时候让子View处理,通过Touch流程可以一一处理.
对于滑动冲突情况,很多博客都有将,参照一下这篇博客.