Android事件分发机制

      在讲之前先提出一个问题 :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;
    }

从上面的源码可以看到,如果我们设置了onTouchListener方法,onTouch是优先于onTouchEvent方法执行的,并且如果onTouch方法返回true时,onTouchEvent不会再执行。

       接下来我们再聊一聊onTouchEvent(MotionEvent ev)。当用户点击手机屏幕,到离开手机屏幕。这期间产生的一系列事件由一个down , 中间数量不定的若干个move 和一个up事件组成,这三种事件类型加起来,我们暂且称他为一次事件集合。我们可以根据MotionEvent.getAction来判断和处理各种事件类型。如果某个View一旦开始处理某个事件集合,就必须消耗掉Action_DOWN事件,也就是说在onTouchEvent必须返回true。否则后续事件不会在传递到当前View,事件会交由上级View的onTouchEvent进行处理。

      最后我们来总结一下

  •       当用户点击屏幕到手指离开屏幕,期间会产生一个Action_down 、数量不定的Action_move(可能是多个,也可能是没有)、一个Action_up,我们称之为一个事件集合,View决定处理一个事件集合是,必须消耗掉Action_down事件,后续事件不会传递到当前View,会交由上级View的onTouch处理;
  • ViewGroup默认不拦截任何事件,从上面的源码也可以看出,Android源码中的ViewGroup的onInterceptTouchEvent默认返回了false;
  • View 中没有onInterceptTouchEvent方法,至于原因,就像上面的小吴,他没有下级让他分发事件。一旦有事件传递给它,那么它的onTouchEvent方法就会被调用。而其返回值受clickable和longclickable属性影响,两者有一个为true,则onTouchEvent默认返回true,二者同为false默认返回false,longclikable默认为false,click要分情况,TextView的click默认为false ,Button,checkBox默认属性为true,这就是为什么在ListView的item中有Button按钮,设置ListView的onItemClick会失效的原因所在。

到这里,Android的事件分发机制基本分析完毕,搞清楚Android的事件分发机制,对于我们事件监听,以及自定义控件,都有非常大的帮助。


你可能感兴趣的:(源码,技巧,事件分发机制,Android事件分发机制,View事件分发机制)