通过简单的demo来解析事件分发机制,主要通过简单的三层Activity–>ViewGrop–>View来分析事件分发机制。先通过一张简单的事件分发流程图来看看事件分发的基本流程。
Activity与View是没有onInterceptTouchEvent这个方法的,也就是说这两者没法使用该方法拦截事件,我们先按照上面流程图来建一个demo,看看事件分发是否与我们流程图所画的一样。如下图:
在没有拦截任何事件的情况下可以看到打印的日志,从Activity开始一层一层的把事件传递给最底层的View,最底层的View的onTouchEvent()没有消耗事件的情况下,又向上依次按照层级去触发onTouchEvent()(在没有消费事件的情况下,也就是返回true),最终还是传递给了最上的Activity的onTouchEvent()来处理事件。然后我们来依次验证其它流程
验证dispatchTouchEvent
- ViewGroup 的dispatchTouchEvent() 返回true,其它方法返回值不变保持super看看事件流程
可以看到dispatchTouchEvent()返回true,事件就直接结束了不会往下传递。Activity或者View的也是一样的,可以自行验证。 - ViewGroup 的dispatchTouchEvent() 返回false或者View的dispatchTouchEvent()返回false的日志图分别如下可以看到两者的日志都如流程图所示,如果返回的是false则会像上一级的onTouchEvent传递。(ps:细心的可能会看到上面的图的日志打印,在日志最后总会多出几个不像流程图里所画的日志,比如MainActivity-----------dispatchTouchEvent(),MainActivity-----------onTouchEvent() 可能会问不是到onTouchEvent()就为止了?这里就需要从源码解释了,由于篇幅问题这里暂时不作详细说明,可以自己去查看源码,事件分发,当你阻止他继续分发后,自身这一层没有消费掉这个事件,还往上层传递的话,下一次判断就直接忽略了,所以直接就是最上层的dispatchTouchEvent()后,就直接onTouchEvent,因为ViewGroup或View的dispatchTouchEvent返回false,没法传递事件。 除非,你抬起手指在点击一次,它才会重新来开始判断,有兴趣的可以从源码分析,这里我截图都是截所有打印的日志)
dispatchTouchEvent()的总结
- 当dispatchTouchEvent()返回false,不像下分发事件时,无论是ViewGroup还是View都会向它的上一层的onTouchEvent()传递(会逆向向上传递),不会执行自身的onTouchEvent()。Activity的则会直接事件结束。
- 当都是默认的super. 则事件将会继续向下分发,直到事件被消费为止。
- 当返回true时,表示事件直接被消费,这个事件也就停止分发且不会逆向向上传递,直接结束了。
验证onInterceptTouchEvent()
1.ViewGroup的onInterceptTouchEvent()返回true,其它方法保持初始状态值
2.ViewGroup的onInterceptTouchEvent()返回false,或者super.onInterceptTouchEvent(ev),则事件会依次向下传递直到被消费为止,就跟初始化的日志图是一样的,这里就不上图了。
onInterceptTouchEvent()的总结
onInterceptTouchEvent()是ViewGroup特有的方法,View和Activity中没有这个方法,当返回false/super时事件将会正常向下分发,分发至下级的dispatchTouchEvent方法;返回true时,则表示ViewGroup容器拦截后续事件,会执行该ViewGroup的onTouchEvent()方法,如果自身onTouchEvent()没有消费掉该事件,则会通过onTouchEvent()向上传递,直到事件被消费
验证onTouchEvent()
1.ViewGroup的onTouchEvent()返回true,自身消耗掉该事件,其它方法不拦截也不消耗事件
2.View的onTouchEvent()返回true
3.ViewGroup或者View的onTouchEvent()返回false,其它保持原样,则日志输出图如正常流程
(ps:这红色框框框起来的部分呢,上面几个图也是,忘记框起来了,就如前面的ps所说。最终消费掉该事件的在哪一层,下次判断就会省去那些没有意义的判断,比如第一个框框图,虽然第一次判断走到了View的onTouchEvent,但是它没有消耗掉该事件,而是向上传递给了ViewGroup的onTouchEvent()消费掉了,源码里面的的判断下一次就会直接省去View里面的判断,直接就是Activity–>Group。也可以这样说就是一个事件一旦交给一个View处理,那么它就必须消耗处理掉,否则同一事件序列中剩下的事件就不再交给它来处理了,短时间内。这里需要自己看源码分析,所以我这里的截图,没必要纠结红色框里面的打印周期)
onTouchEvent()的总结
返回true则立即消费掉事件,事件将不会向上传递,事件到此终止。返回false/super则不消费掉此次事件,事件将会层层向上传递,直到被消费。
总结一下事件分发中这几个方法的返回不同值时的表现
- dispatchTouchEvent和onTouchEvent这两个方法呢无论是Activity,ViewGroup还是View,只要返回了true就表示,该事件到此就终止了,不会往下或者上传递事件了,什么都结束了。
- dispatchTouchEvent和onTouchEvent这两个方法返回false/super时呢,对于ViewGroup或者View来说,事件都回传给父控件的onTouchEvent处理。如果是Activity呢就直接结束了,因为它是最上层了到此就终止了。
- onInterceptTouchEvent方法默认是不会去拦截事件的,因为子View也需要这个事件,所以onInterceptTouchEvent()拦截器返回 super.onInterceptTouchEvent() 和 false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent()传递;如果需要拦截呢就要返回true。
(ps:最好自己根据上面的流程图写代码跟踪一下日志,就清晰了)
关于onTouch(),onClick()的优先级和影响
给view设置setOnTouchListener和setOnClickListener事件,其它方法默认super,初始化看一下日志输出
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("TAG","EventView-----------OnTouchListener()");
return false;
}
});
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("TAG","EventView-----------OnClickListener()");
}
});
在默认都不拦截的情况下,onTouch的优先级高于onTouchEvent,onTouchEvent高于onClick.
我们将onTouch返回值改为true看看
可以看到onTouch返回true以后View的包含onTouchEvent()事件的后面方法都不会执行了。被onTouch()消耗完了。onClick()是优先级最低的。在源码里面我们可以看到,这里贴上的是View里面的部分源码,ViewGroup的也差不多。在dispatchTouchEvent()的方法中
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;//是否消耗事件
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//若设置了OnTouchListener,则先调用onTouch()。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//若onTouch()没有消耗事件则调用onTouchEvent()
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
onTouchEvent()中则包含onClick的代码块事件中的performClickInternal()就能找到点击事件
public boolean onTouchEvent(MotionEvent event) {
......
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
}
可以看到mOnTouchListener.onTouch的优先级是高于onTouchEvent的。
onClick(),setOnClickListener()时,如果该View的onTouchEvent()不是super.onTouchEvent(event)时,无论设置是return false还是true该事件不会响应。如下图
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("TAG","EventView-----------onTouchEvent()");
// return super.onTouchEvent(event);
return false;
}
设置false
设置true
只有当View的onTouchEvent()返回super.onTouchEvent(event)时,setOnClickListener()才会生效,默认为消费了该事件,不会往上在传递给ViewGroup的onTouchEvent()处理。
ViewGroup中的requestDisallowInterceptTouchEvent() 设置是否允许拦截
在ViewGroup中如果在onInterceptTouchEvent()中拦截了事件,但是子View在某些情况下又需要该事件怎么办?在ViewGroup的dispatchTouchEvent()的源码中我们可以发现disallowIntercept 设置是否拦截,跟随这个我们可以看到requestDisallowInterceptTouchEvent()设置为true的时候,可以不拦截onInterceptTouchEvent()
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
// 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;
}
......
}
@Override
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);
}
}
我们修改一下EventView和EventGroup中的代码,使其在滑动的时候拦截事件Group自己消耗
EventView
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// getParent().requestDisallowInterceptTouchEvent(true);
Log.i("TAG", "You down EventView");
break;
case MotionEvent.ACTION_UP:
Log.i("TAG", "You up EventView");
break;
case MotionEvent.ACTION_MOVE:
Log.i("TAG", "You move EventView");
}
return true;
}
EventGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
return false;
case MotionEvent.ACTION_MOVE: //表示父类需要
return true;
case MotionEvent.ACTION_UP:
return true;
default:
break;
}
return false; //如果设置拦截,除了down,其他都是父类处理
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
由于我们EventGroup在滑动的时候,return true自己消耗了事件,则EventView不会响应事件输出日志
但是我们要在滑动的时候EventView也要响应自己的事件,把上面我们注释的代码块解除看看日志
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
Log.i("TAG", "You down EventView");
break;
case MotionEvent.ACTION_UP:
Log.i("TAG", "You up EventView");
break;
case MotionEvent.ACTION_MOVE:
Log.i("TAG", "You move EventView");
}
return true;
}
很明显getParent().requestDisallowInterceptTouchEvent(true); 这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。也就是说子元素能够通过调用requestDisallowIntercept(boolean b)来控制父容器能否调用onInterceptTouchEvent(),是否进行事件拦截。