Android 的事件传递机制,详细解释

前两天和一个朋友聊天的时候,然后说到事件传递机制,然后让我说的时候,忽然发现说的不是很清楚,其实Android 的事件传递机制也是知道一些,但是感觉自己知道的很模糊,仅仅是知道事件是从外层先传到内层,在从内存最后回馈到外层,但是详细的几个方法的调用过程,自己却知道的不是很详细,我想很多人都是这种情况,然后自己就上网去查,然后看到的全部都是在讲会调用哪个几个方法,但是基本没有讲这几个方法的具体作用。自己回去写了demo ,今天就把自己的理解写出来,希望大家能更详细了解这几个方法的具体作用。

首先事件传递会用到的几个方法,我们先列举一下。

  1. public boolean onInterceptTouchEvent(MotionEvent ev)
  2. public boolean dispatchTouchEvent(MotionEvent ev)
  3. public boolean onTouchEvent(MotionEvent ev)

我们查看源码,可以看到,onInterceptTouchEvent 这个方法是viewGroup 的方法

但是 dispatchTouchEvent 和 onTouchEvent 都是view 方法。

那么我们先写一个View OutView(外层view)继承ViewGroup ,MiddleView(中层View) 继承ViewGroup ,InnerView (内层Button,Button 也是继承的view )继承View

既然 dispatchTouchEvent 和 onTouchEvent 是view 的方法,那么我们在每个view 里面重写这两个方法,如下,

每个方法会打印两边log,第一个log,是调用这个方法开始的时候,就打一句,第二个log ,是这个方法调用父类,也就说,该走的逻辑走完,并且有返回值的时候。为了log 清晰,都只是打印了 ACTION_DOWN 事件的。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent");
                break;
        }

        boolean flag = super.dispatchTouchEvent(ev);

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent:" + flag);
                break;
        }


        return flag;
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {


        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent");
                break;
        }

        boolean flag = super.onTouchEvent(ev);

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent:" + flag);
                break;
        }


        return flag;


    }

输出的log 如下

toucheventdemo E/OutView﹕ dispatchTouchEvent
toucheventdemo E/MiddleView﹕ dispatchTouchEvent
toucheventdemo E/InnerView﹕ dispatchTouchEvent
toucheventdemo E/InnerView﹕ onTouchEvent
toucheventdemo E/InnerView﹕ onTouchEvent:true
toucheventdemo E/InnerView﹕ dispatchTouchEvent:true
toucheventdemo E/MiddleView﹕ dispatchTouchEvent:true
toucheventdemo E/OutView﹕ dispatchTouchEvent:true

可以看到前三个虽然都是直接都是dispatchTouchEvent 方法里面的log 打印的,但是log并没有返回值,也就是说,都是执行的方法里面第一句log,
但是当执行到InnerView﹕ dispatchTouchEvent 的时候,又执行了InnerView﹕ onTouchEvent 的方法

其实是这样的, boolean flag = super.dispatchTouchEvent; 这一句,super 中,执行的dispatchTouchEvent ,
其实会做两件事

1,第一件事,是看当前的view 有没有子view,如果有子view,并且会判断当前的触摸点是否在子view 上面,如果在的话,那么就会调用子view的dispatchTouchEvent  ,然后一层一层往里面调用,
2.一直调用到没有子view 的时候,然后停止dispatchTouchEvent ,因为InnerView 是继承的view,当然没有子view,然后这个时候,就开始调用子view 的onTouchEvent。

onTouchEvent 返回 true ,那么当前view 的dispatchTouchEvent  也会返回view, 此时,就会讲dispatchTouchEvent 的分发结果反馈给父view,当前view 的父view 的dispatchTouchEvent 也会返回true,以此类推,返回到最外层,

那么如果里层的view 返回的true 那如果最里层的view onTouchEvent返回的是false 那?我们将里层的view 继承改成View,

toucheventdemo E/OutView﹕ dispatchTouchEvent
toucheventdemo E/MiddleView﹕ dispatchTouchEvent
toucheventdemo E/InnerView﹕ dispatchTouchEvent
toucheventdemo E/InnerView﹕ onTouchEvent
toucheventdemo E/InnerView﹕ onTouchEvent:false
toucheventdemo E/InnerView﹕ dispatchTouchEvent:false
toucheventdemo E/MiddleView﹕ onTouchEvent
toucheventdemo E/MiddleView﹕ onTouchEvent:false
toucheventdemo E/MiddleView﹕ dispatchTouchEvent:false
toucheventdemo E/OutView﹕ onTouchEvent
toucheventdemo E/OutView﹕ onTouchEvent:false
toucheventdemo E/OutView﹕ dispatchTouchEvent:false

我们会发现,当里层的view ,onTouchEvent:false 的时候,那么里层的dispatchTouchEvent 也是false ,并且此时立马会调用,里层view 的父view(也就是MiddleView) 的 onTouchEvent 事件,MiddleView的onTouchEvent 也返回false ,那么此时MiddleView dispatchTouchEvent 也会是false,然后接着调用了最外层 OutView onTouchEvent 方法。

从这两个小试验,你能看出,dispatchTouchEvent 的返回结果是怎么得来的吗?

分两种情况

  1. 当前 没有子view 那么当前view 的 dispatchTouchEvent 结果就是当前view onTouchEvent 的结果
  2. 当前view 有子view, 此时又分两种情况
  1. 子view 的 dispatchTouchEvent 返回false 那么当前view 的 dispatchTouchEvent 结果参照第一种情况,也就是当前view onTouchEvent 执行 ,并且dispatchTouchEvent 的结果是 当前view 的 onTouchEvent 的结果
  2. 子view 的 onTouchEvent 返回true ,那么 此时当前view 的onTouchEvent 的不执行 直接直接返回的是子view 的 dispatchTouchEvent 返回的view
将两种情况再总结一下,就是,当前view的 dispatchTouchEvent 的返回结果是:如果子view 的 dispatchTouchEvent 返回true ,那么当前view 不执行onTouchEvent ,当前view 的 dispatchTouchEvent 直接返回true, 如果子view 的dispatchTouchEvent 返回false ,当前view 的 dispatchTouchEvent的结果看 看当前view 的onTouchEvent 结果。

看到这里,你是否对, dispatchTouchEvent onTouchEvent 稍微有一些理解了那?其实我们看方法的名称也应该能理解一些,dispatchTouchEvent 翻译:触摸事件分发, onTouchEvent 触摸事件,也就是说dispatchTouchEvent 主要做的是处理的触摸事件的分发管理,用来告诉父view 当前view 有没有处理我们的触摸事件,并且处理,当前触摸的点,还有没有子view, onTouchEvent 用来处理我们具体的触摸事件。

(具体这里为什么说还要判断当前触摸的点还有没有子view,你可以自己试验,触摸MiddleView 之内,InnerView之外的区域,看会不会打印InnerView 里面的log )

说了半天,我们一直在说dispatchTouchEvent onTouchEvent 这两个方法,那么onInterceptTouchEvent 这个方法又是干嘛的那?

首先翻译api onInterceptTouchEvent : 拦截TouchEvent 事件。

应为onInterceptTouchEvent 是viewGroup 的方法,那么我们重写OutView 和 MiddleView 的onInterceptTouchEvent方法

public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent");
                break;
        }

        boolean flag = super.onInterceptTouchEvent(ev);


        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent:" + flag);
                break;
        }


        return flag;
    }


然后运行,点击InnerView  看log

toucheventdemo E/OutView﹕ dispatchTouchEvent
toucheventdemo E/OutView﹕ onInterceptTouchEvent
toucheventdemo E/OutView﹕ onInterceptTouchEvent:false
toucheventdemo E/MiddleView﹕ dispatchTouchEvent
toucheventdemo E/MiddleView﹕ onInterceptTouchEvent
toucheventdemo E/MiddleView﹕ onInterceptTouchEvent:false
toucheventdemo E/InnerView﹕ dispatchTouchEvent
toucheventdemo E/InnerView﹕ onTouchEvent
toucheventdemo E/InnerView﹕ onTouchEvent:false
toucheventdemo E/InnerView﹕ dispatchTouchEvent:false
toucheventdemo E/MiddleView﹕ onTouchEvent
toucheventdemo E/MiddleView﹕ onTouchEvent:false
toucheventdemo E/MiddleView﹕ dispatchTouchEvent:false
toucheventdemo E/OutView﹕ onTouchEvent
toucheventdemo E/OutView﹕ onTouchEvent:false
toucheventdemo E/OutView﹕ dispatchTouchEvent:false

我们会发现,在所有的具体执行 dispatchTouchEvent 的方法一开始执行,都会先执行当前view 的onInterceptTouchEvent 结果,onInterceptTouchEvent 的默认返回值是false

现在我们修改一下MiddleView 的onInterceptTouchEvent 返回值为true 看代码

public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent");
                break;
        }

        boolean flag = super.onInterceptTouchEvent(ev);


        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent:" + true);
                break;
        }


        return true;
    }

然后我们运行,看log

toucheventdemo E/OutView﹕ dispatchTouchEvent
toucheventdemo E/OutView﹕ onInterceptTouchEvent
toucheventdemo E/OutView﹕ onInterceptTouchEvent:false
toucheventdemo E/MiddleView﹕ dispatchTouchEvent
toucheventdemo E/MiddleView﹕ onInterceptTouchEvent
toucheventdemo E/MiddleView﹕ onInterceptTouchEvent:true
toucheventdemo E/MiddleView﹕ onTouchEvent
toucheventdemo E/MiddleView﹕ onTouchEvent:false
toucheventdemo E/MiddleView﹕ dispatchTouchEvent:false
toucheventdemo E/OutView﹕ onTouchEvent
toucheventdemo E/OutView﹕ onTouchEvent:false
toucheventdemo E/OutView﹕ dispatchTouchEvent:false

我们会发现,当修改了,MiddleView 的onInterceptTouchEvent 返回结果是true 之后,
本来应该向InnerView 做分发的那部分log 没有了。然后直接执行了当前MiddleView 的 onTouchEvent 结果,然后执行完
onTouchEvent结果,之后,当前view 的dispatchTouchEvent 的结果也出来了,然后紧接着把这个有反馈给了OutView 外层view 的,并且执行的原理和我们前面只重写 dispatchTouchEvent onTouchEvent 的原理一样。

到这里你也应该知道这个onInterceptTouchEvent 是干嘛的吧,他其实就是拦截事件是否要继续往下传递,如果返回true ,那么代表 事件被当前view 拦截,并且不向下传递,直接执行当前view 的 onTouchEvent 事件。

到这里,我想应该清楚这几个方法的含义了吧。

  1. onInterceptTouchEvent : 拦截事件,判断是否向下传递。 返回true 代表拦截,返回false 代表不拦截
  2. dispatchTouchEvent : 事件分发,管理事件的分发,向子view 分发事件,并且,根据得到的子view的事件处理情况,来判断是否响应当前view的 触摸事件 返回true,代表当前view 的或者子view 已经处理事件,告诉父view, 不需要处理事件,返回false ,代表当前view 或者子view 也没有处理事件,告诉父view,可以去处理事件了。
  3. onTouchEvent : 事件的处理,在此方法内处理事件。 返回true ,代表当前view 处理了事件,false 代表当前view 没有处理事件。

然后我们再看另外一个可能常用的方法,

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)

首先翻译: 请求禁止拦截触摸事件 ,字面理解,就是禁止吊我们的拦截,也就是禁止吊这个方法,onInterceptTouchEvent

那么我们现在把所有的onInterceptTouchEvent dispatchTouchEvent onTouchEvent 都加上move 事件。

代码如下,

public boolean onInterceptTouchEvent(MotionEvent ev) {



        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
                break;
        }

        boolean flag = super.onInterceptTouchEvent(ev);


        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN:" + flag);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE:" + flag);
                break;
        }


        return flag;
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                break;
        }

        boolean flag = super.dispatchTouchEvent(ev);

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent ACTION_DOWN:" + flag);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent ACTION_MOVE:" + flag);
                break;
        }


        return flag;
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {


        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent ACTION_MOVE" );
                break;
        }

        boolean flag = super.onTouchEvent(ev);

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent ACTION_DOWN:" + flag);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent ACTION_MOVE:" + flag);
                break;
        }


        return flag;


    }

并且给 MiddleView 的onInterceptTouchEvent 方法中加上 requestDisallowInterceptTouchEvent(true);
如下

public boolean onInterceptTouchEvent(MotionEvent ev) {

        //加上此句
        requestDisallowInterceptTouchEvent(true);
        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
                break;
        }

        boolean flag = super.onInterceptTouchEvent(ev);


        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN:" + flag);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE:" + flag);
                break;
        }


        return flag;
    }

那么现在我们触摸,然后看log ,可能log 比较多,但是希望大家慢慢仔细看。

toucheventdemo E/OutView﹕ dispatchTouchEvent ACTION_DOWN
toucheventdemo E/OutView﹕ onInterceptTouchEvent ACTION_DOWN
toucheventdemo E/OutView﹕ onInterceptTouchEvent ACTION_DOWN:false
toucheventdemo E/MiddleView﹕ dispatchTouchEvent ACTION_DOWN
toucheventdemo E/MiddleView﹕ onInterceptTouchEvent ACTION_DOWN
toucheventdemo E/MiddleView﹕ onInterceptTouchEvent ACTION_DOWN:false
toucheventdemo E/InnerView﹕ dispatchTouchEvent ACTION_DOWN
toucheventdemo E/InnerView﹕ onTouchEvent ACTION_DOWN
toucheventdemo E/InnerView﹕ onTouchEvent ACTION_DOWN:true
toucheventdemo E/InnerView﹕ dispatchTouchEvent ACTION_DOWN:true
toucheventdemo E/MiddleView﹕ dispatchTouchEvent ACTION_DOWN:true
toucheventdemo E/OutView﹕ dispatchTouchEvent ACTION_DOWN:true

//从这里开始move事件。
toucheventdemo E/OutView﹕ dispatchTouchEvent ACTION_MOVE
toucheventdemo E/MiddleView﹕ dispatchTouchEvent ACTION_MOVE
toucheventdemo E/InnerView﹕ dispatchTouchEvent ACTION_MOVE
toucheventdemo E/InnerView﹕ onTouchEvent ACTION_MOVE
toucheventdemo E/InnerView﹕ onTouchEvent ACTION_MOVE:true
toucheventdemo E/InnerView﹕ dispatchTouchEvent ACTION_MOVE:true
toucheventdemo E/MiddleView﹕ dispatchTouchEvent ACTION_MOVE:true
toucheventdemo E/OutView﹕ dispatchTouchEvent ACTION_MOVE:true
toucheventdemo E/OutView﹕ dispatchTouchEvent ACTION_MOVE
toucheventdemo E/MiddleView﹕ dispatchTouchEvent ACTION_MOVE
toucheventdemo E/InnerView﹕ dispatchTouchEvent ACTION_MOVE
toucheventdemo E/InnerView﹕ onTouchEvent ACTION_MOVE
toucheventdemo E/InnerView﹕ onTouchEvent ACTION_MOVE:true
toucheventdemo E/InnerView﹕ dispatchTouchEvent ACTION_MOVE:true
toucheventdemo E/MiddleView﹕ dispatchTouchEvent ACTION_MOVE:true
toucheventdemo E/OutView﹕ dispatchTouchEvent ACTION_MOVE:true

我们可以看处理啊,第一次是ACTION_DOWN 事件的时候一切正常,(InnerView 是继承的 Button,所以onTouchEvent 返回的是true)

但是从ACTION_MOVE 你会发现,所有的onInterceptTouchEvent 事件全部没有了,其实,也就这个方法也就是这个方法起的作用,

好了,所有的东西讲完了,不知道你现在是否对事件传递有足够的理解那?