假设手机屏幕上有一个button。我们去点击了一下,然后button做出了相应的反应,那么这个过程其实这样的,当手机点击到屏幕时,TP(Touch panel)传感器的数据发生了变化。数据经过驱动的处理(其实用示波器来看传感器数据,这个数据肯定不可能那么规整),然后数据依次传递到内核,framwork,然后再传递相应的app的当前Activity中。我们说android中View的事件分发,其实指的就是在此之后的过程。当手指接触屏幕所产生的反应,我们称之为事件(MotionEvent).
当然MotionEvent 实际上也可以细分为很多种,典型的有以下几种。

  • ACTION_DOWN: 手指刚刚接触到屏幕。

  • ACTION_MOVE:手指在屏幕上滑动。

  • ACTION_UP:手指从屏幕上离开的一瞬间。

所以正常情况下,手指触摸屏幕会触发一系列事件。
比如 DOWN -> MOVE -> UP。从手指接触屏幕那一刻起,到手指离开屏幕那一刻结束,中间所产生的一系列事件总和,我们称之为一个事件序列。它以一个down事件开始,中间夹杂着0个或多个move事件,以一个up事件结束。

我们手指接触到屏幕,怎么判断是点击还是滑动呢?其实在手机framework中,有一个默认值,比如8px(和具体的设备和手机厂商有关),当手指移动的距离大于该值时,就是滑动事件TouchSlop,否则就是点击事件。

我们所讨论的事件分发,指的就是 MotionEvent从 Activity -> Window -> ViewGroup -> View这个过程。 (毕竟我们都是 做上层app开发的,像TP数据怎么处理之类的那些是驱动工程师们关心的问题)。

这个事件分发的过程,总结起来其实很简单,因为该场景和我们日常生活中的场景是一致的。

boss(老板)要做某件事情,但是既然身为boss,人家肯定不用自己动手嘛,所以这事交给下面的manager(经理)就好了,经理好歹也是领导嘛,所以也不用自己亲自动手,交给下面的worker(工人)就好了,所以转了一圈,干活的还是最下面的工人,如果worker把这活处理了,那么这件事情就算完了,可是也有例外情况,如果worker发现这个活自己处理不了,那么这事就只能向上反馈给manager 了,如果manager能把这活给处理了,那自然最好,可是如果manager发现这活自己也处理不了,那就只能再反馈给boos,让boss去处理了

事件分发也是如此。点击屏幕上的一个点,事件那么经过activity -> window,然后传递到 界面最外层(或者叫最顶级)的ViewGroup容器中(指的是Relativelayout,LinearLayout等)。 然后最后依次传递到被包裹在最里面到 View中。(当然,如果View包裹的有几层的ViewGroup,那么肯定是依次先传递给它们, 并且需要注意,最里面的控件完全可以是一个ViewGroup,而它不必包含View。)

事件被传递到了最里面的View,所以交给了View来处理,如果View没处理,那么就依次倒过来向上回溯。分别交给各级ViewGroup,如果它们也没处理,那么就继续向上回溯,乃至Window,Activity中来处理。

View就是最底层的worker,而boss,manager都是包裹在它外面的ViewGroup,如果层级比较复杂的话,还要经过多层才能传到到最底层的View,(比如经过老板,总经理,部门经理,项目经理等多层,事情才分配到最底层的工人这里), View处理不了的,那么就再依次向上反馈给各级领导,看看谁去处理。

该过程其实就是一个递归的过程。这是android系统中默认的事件分发规则,如果我们理解了这个规则,那么我们就可以解决很多实际问题,比如 我们可以在某一层ViewGroup中来拦截处理事件,而不用向下传递到View中。

Notice:

  1. 因为ViewGroup extends View,所以从继承关系上来讲,ViewGroup也是一个View,而在本文当中说的ViewGroup指的是 RelativeLayoutLinearLayout 等容器类View,而View指的是Button,TextView等不会再有child的View(因为它们不会再包裹什么了)。之所以要明确区分,是因为它们的有些方法的实现是有区别的。

  2. 为了方便,在本文当中,称呼控件指的是View或ViewGroup(也可能是View和ViewGroup)。

  3. 在本文当中,一个控件处理了事件,并return了对应的boolean值,我们称之为 事件被 消费了。(如果没有return值,我们怎么知道这个事件是否被处理了呢?)

既然是事件分发,那么不得不提到到几个主要方法。

View中:

     //从名字中就可以看出来,是负责分发事件的方法。
     public boolean dispatchTouchEvent(MotionEvent event)
    
    //该方法中设置去消费事件,比如我们平时常用的OnClickView,OnLongClick等监听事件都是在这里被调用的。
    public boolean onTouchEvent(MotionEvent event)

实际上,View当中也有具体负责消费事件的接口。

    public interface OnTouchListener {        boolean onTouch(View v, MotionEvent event);
    }    
    // 这个就是我们最常用的 点击事件方法。
    public interface OnClickListener {        void onClick(View v);
    }

说完了View,那么再来看看ViewGroup中的主要方法。

    //View中也有该方法,但是和ViewGroup中的实现方式不同
    public boolean dispatchTouchEvent(MotionEvent ev) 
    
    //该方法是ViewGroup特有的,决定是否拦截事件。(默认返回false),如果返回true,则事件不会再向child传递了,这个道理很简单,ViewGroup是属于领导, 它才有权利决定是否拦截事件。而View是最底层的工人,是没有决定权的。
    public boolean onInterceptTouchEvent(MotionEvent ev)
    
    
    
    //几个相关方法或接口中,ViewGroup实现了这两个,其他都没重写,也就是利用View当中的实现

和事件分发有关的方法就这几个。基本的道理其实也不复杂。(联想老板给工人分配任务的场景就可以了)。
而关于这个过程,可以使用伪代码描述如下:
对于View:

//@author www.yaoxiaowen.comboolean dispatchTouchEvent(MotionEvent event){    boolean result = false;    if (OnTouchListener.OnTouch(view, event)){
        result = true;
    }    if (!result && onTouchEvent(event)){
        result = true;
    }    return result;
}//实际上这个类,处理的比较复杂,比如分为 ACTION_DOWN, ACTION_UP 之类的,//不过这里是仅仅是作为示例的伪代码。//@author www.yaoxiaowen.comboolean onTouchEvent(MotionEvent event){    if (clickable || clickable){        if (event.getAction()==ACTION_UP && OnClickListener!=null){
            OnClickListener.onClick();
        }        return true;
    }    return false;
}

而对于 ViewGroup,伪代码如下:

//@author www.yaoxiaowen.comboolean dispatchTouchEvent(MotionEvent event){    boolean result = false;    if (onInterceptTouchEvent(event)){        //Notice:这个地方不是调用 的this.onTouchEvent。调用自己那就是死循环了,既然调用了父类View的dispatchTouchEvent(),想想View的该方法做什么,所以实际上就是把事件分发给自己了。
        //(因为类似onTouchEvent, onTouchListener方法在ViewGroup当中没实现,只在父类View当中才实现)
        reuslt = super.dispatchTouchEvent();
    }else{         //child可能是 ViewGroup,也可能是 View
        result = child.dispatchTouchEvent(event);
    }    return result;
}

结合伪代码,再想象具体的分发流程。仔细琢磨一下,就可以大概的了解这个分发流程了。

虽然总结的内容的不复杂,但是实际上依旧有很多的细节。下面我们就结合具体的源码来进行分析。(具体的源码非常复杂,参考了不少博客,然后自己也读了几遍,但是也只是理解了一小部分,毕竟自己也水平有限。不过我们要分析的也就是流程的主干内容。)

沿着事件分发的流程。Activity -> Window -> ViewGroup -> View.

先看Activity:

 //Activity.javapublic boolean dispatchTouchEvent(MotionEvent ev) {    //省略部分代码

    //从这句代码中,将事件分分发到了 Window 
    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中,事件被传递给了Window,但是此时要注意了,如果Window当中未消费事件。(就是返回了false)。那么此时就调用Activity自身的onToucheEvent()了。这在后面的流程中,原理是相同的。就是根据返回值来判断是否消费了事件,整个事件分发机制从上向下,再从下到上这套递归的回溯机制,就是如此。

Activity当中把事件分发给了Window,不过Window类是abstract的,Window的唯一实现 就是PhoneWindow,然后这中间的传递过程就比较复杂(因为中间很多源码其实我也没看懂),反正最后就传递到了Activity布局中最外层的ViewGroup中。
ViewGroup中的dispatchTouchEvent方法的实现就比较长了,所以我们这里就先从上到下分片段的来看这个方法的具体实现。

//ViewGroup.java当中的代码@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    //首先,在该方法的开头,并没有 super()的代码,这说明 dispatchTouchEvent方法就是在ViewGroup当中实现的,View和ViewGroup当中各有一套实现规则。

    //......

    boolean handled = false;   
        //......

        // Check for interception.
        final boolean intercepted;        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;            if (!disallowIntercept) {                //Notice:注意 这个地方调用了 onInterceptTouchEvent 方法来判断在该ViewGroup当中是否拦截。
                // onInterceptTouchEvent 默认的返回值 是false。
                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;
        }        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

而如果在ViewGroup中不拦截该事件,或者该事件不该在本ViewGroup当中处理,那么就会遍历它的child进行处理。

    //ViewGroup.java 的 dispatchTouchEvent(MotionEvent event)  方法
  // ...... 

                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {                                if (childWithAccessibilityFocus != child) {                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }                          //......

                            resetCancelNextUpFlag(child);                            //注意这一句调用,将事件往下进行分发。
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                     http://www.cnblogs.com/yaoxiaowen/p/6925702.html