在讲之前先提出一个问题 :ListView的Item中有Button,onItemClick为什么会失效?
在讲Android时间分发机制之前,我们需要知道三个非常重要的方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,通过查看Android源码可以看到,在View类中声明了dispatchTouchEvent和onTouchEvent方法并没有onInterceptTouchEvent。那onInterceptTouchEvent方法在哪里呢?通过查看View的子类ViewGroup的源码可以看到,onInterceptTouchEvent是在ViewGroup中声明,并默认返回了false。
// ViewGroup类中 public boolean onInterceptHoverEvent(MotionEvent event) { return false; }下面我们先来看看这三个方法的具体作用。
public void dispatchTouchEvent(MotionEvent ev)
用于事件的分发。如果事件传递给当前的View,那么这个方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响。
pubic void onInterceptTouchEvent(MotionEvent ev)
是否拦截某个事件,返回false,不拦截事件,向子View进行分发(默认返回的是false)。返回true,则会中断事件传递,并把事件交由当前View的onTouchEvent处理。
public void onTouchEvent(MotionEvent ev)
事件处理,返回结果表示是否消耗当前事件
简单分析了这三个方法之后,相信大部分童鞋还是有点不清楚这三个方法之间的关系和区别。这里就借用任大神的在他书中一段伪代码来给讲解
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false; // 判断是否中断当前事件 if(onInterceptTouchEvent(ev)){ // 如果onInterceptTouchEvent返回true 则调用当前View的onTouchEvent()方法 consume = onTouchEvent(ev); }else{ // 如果返回false 则调用子View的dispatchTouchEvent方法,向下分发事件 consume = childView.dispatchTouchEvent(ev); } return consume; }
通过这样一段伪代码,相信大家应该都初步理解的Android的事件传递机制。接下来我们接着向下分析。通过上面的分析我们得知,当一个事件发生之后,事件的传递顺序如下:Activity接收到事件 ->Window ->根Vew ->子view ->子View 。当Activity接收到事件,会传递给Window,而Window会传递个顶级View,然后根据上面我们分析的机制进行事件的分发传递,直到有View消耗此事件。如果当前View的onTouchEvent方法返回false,则上级View的onTouchEvent方法就会执行。依次类推,如果所有的View都没有处理此事件,最终就会回到Activity手里,交由Activity进行处理。
举个简单的例子,外包公司客户想Boss提交了一个Bug,Boss一看这的改啊,但Boss肯定不会亲自改,于是找来项目经理,“小李啊,来这有个Bug,去解决一下”,项目经理拿到Bug,经理嘛,怎能随便出山。于是经理又叫来项目组长,“小曹,把这个bug搞定”。可项目组长此时正忙着开发新项目,没时间。于是把bug有丢给新来的程序员小吴。小吴没办法,手下没人了,心想尼玛,欺负我新来的。于是找到组长,“老大,这玩意不会”,组长也懒得看bug,直接丢给经理,“不会”。经理一看,这要是不解决又要被老板批了,于是噼里啪啦把bug解决了。
这里的Boss 就相当于Activity,而经理、项目组长相当于ViewGroup,而小吴相当于View ,Boss,经理,组长,小吴,都拥有解决问题的能力。这就相当于每一层都拥有事件处理的能力,而无论在哪一层进行了事件的处理,事件都会被消耗掉。如果Boss收到Bug之后,项目经理正好闲的蛋疼。把bug解决了,也就没有组长小吴什么事了。但如果
大家都不想解决这个bug,最终一层抛一层。最终这个bug又会抛回到Boss手里。
我们接着向下分析,这是View中的dispatchTouchEvent的源码
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; // 从这里我们可以看到,如果为当前View设置了onTouchListener,则onTouch方法会优先于onTouchEvent方法执行 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
接下来我们再聊一聊onTouchEvent(MotionEvent ev)。当用户点击手机屏幕,到离开手机屏幕。这期间产生的一系列事件由一个down , 中间数量不定的若干个move 和一个up事件组成,这三种事件类型加起来,我们暂且称他为一次事件集合。我们可以根据MotionEvent.getAction来判断和处理各种事件类型。如果某个View一旦开始处理某个事件集合,就必须消耗掉Action_DOWN事件,也就是说在onTouchEvent必须返回true。否则后续事件不会在传递到当前View,事件会交由上级View的onTouchEvent进行处理。
最后我们来总结一下
到这里,Android的事件分发机制基本分析完毕,搞清楚Android的事件分发机制,对于我们事件监听,以及自定义控件,都有非常大的帮助。