Android事件传递机制(二)情景分析

在上一篇文章 Android事件传递机制(一)流程梳理 中我们梳理了事件传递的流程,前文的分析我们好像是将一个事件作为一个整体来分析的,其实不然。前文中我们也提到了当onTouchEvent()方法返回true表示处理事件时,日志流程会打印多次,这是因为TouchEvent事件包含ACTION_DOWN、ACTION_MOVE、ACTION_UP等事件,当onTouchEvent()方法返回true时ACTION_MOVE、ACTION_UP等后续事件就会传递给相应控件,现在我们来具体分析此类情况。

正如前文所说:我们讨论的事件是指TouchEvent,可以称之为触摸事件,它包含ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)、ACTION_CANCEL等。正常情况下,一次触摸手机屏幕的行为,起点是由ACTION_DOWN事件开始的,然后产生一系列的ACTION_MOVE事件,最后通常以ACTION_UP事件结束。并且我们现在已经知道了事件传递的方向是:自上而下由父控件传递给子控件

所以我们需要理解的是:当ACTION_DOWN事件产生的时候,会由父控件传递给子控件,ACTION_MOVE事件也是由父控件传递给子控件,ACTION_UP事件也是由父控件传递给子控件。它们都分别遵循同样的传递事件的逻辑流程。

一、前面事件响应的结果会影响到后续事件的执行

1、onTouchEvent()的返回值对后续事件的影响:

还是以前文的模型为例进行说明,ViewGroupA包含了一个ViewGroupB,ViewGroupB中又包含了一个子View:

此时我们修改dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()方法中的返回值和日志打印(只列出变化部分,其余不变):

ViewGroupA中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...onInterceptTouchEvent: false, ACTION = " + ev.getAction());
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupA...onTouchEvent: false, ACTION = " + event.getAction());
        return false;
    }

ViewGroupB中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...onInterceptTouchEvent: false, ACTION = " + ev.getAction());
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupB...onTouchEvent: false, ACTION = " + event.getAction());
        return false;
    }

子View中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...onTouchEvent: false, ACTION = " + event.getAction());
        return false;
    }

其中getAction()能够获取到具体的事件。具体来说ACTION_DOWN = 0;ACTION_UP = 1;ACTION_MOVE = 2;ACTION_CANCEL = 3。同样轻触SonView区域,得到如下日志结果:

事件传递流程还是和前文一样,但是出现一个问题:不是说正常情况下,一次触摸事件,起点是由ACTION_DOWN事件开始的,然后产生一系列的ACTION_MOVE事件,最后通常以ACTION_UP事件结束。但是为什么现在只有ACTION_DOWN事件,后续的ACTION_MOVE事件和ACTION_UP事件去哪里了?

如果此时我们再次修改子View中的方法,让onTouchEvent()方法返回true:

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...onTouchEvent: true, ACTION = " + event.getAction());
        return true;
    }

轻触SonView,打印日志:

此时可以看出ACTION_DOWN、ACTION_MOVE、ACTION_UP先后执行,并依次按照上图的事件传递流程进行传递。也就验证了我们上面的结论:当ACTION_DOWN事件产生的时候,会由父控件传递给子控件,ACTION_MOVE事件也是由父控件传递给子控件,ACTION_UP事件也是由父控件传递给子控件。它们都分别遵循同样的传递事件的逻辑流程。

接下来分析第二次的结果和第一次进行对比: 第一次时,ACTION_DOWN事件传递到了子View上,但是子View的onTouchEvent()方法对于这个ACTION_DOWN事件的处理是return了一个false,这样就会造成父View的onTouchEvent()方法的调用,同时还有另外一个后果,那就是后续的ACTION_MOVE、ACTION_UP事件就都传递不到子View上了。同样父View(即ViewGroupB)的onTouchEvent()方法对于这个ACTION_DOWN事件的处理也是return了一个false,那么后续的ACTION_MOVE、ACTION_UP事件就都传递不到ViewGroupB上了。同理ViewGroupA的onTouchEvent()方法也return了一个false,所以后续的ACTION_MOVE、ACTION_UP事件也就都传递不到ViewGroupA上了,所以第一次的日志中也就没有了后续的ACTION_MOVE和ACTION_UP事件。

简单来说,如果你在onTouchEvent()方法中执行ACTION_DOWN事件的时候返回了false,那么该控件后面一系列其它的ACTION就不会再得到执行了。其实就是当dispatchTouchEvent()方法在进行事件分发的时候,只有前一个ACTION返回true,那么后一个ACTION才会传递到该控件。所以如果一个View要处理滑动事件,也就是ACTION_MOVE事件的话,那么它一定不能在onTouchEvent()方法中在ACTION_DOWN事件时返回false。

       2、onInterceptTouchEvent()的返回值对后续事件的影响:

同样我们修改dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()方法中的返回值和日志打印:

ViewGroupA中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...onInterceptTouchEvent: false, ACTION = " + ev.getAction());
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupA...onTouchEvent: false, ACTION = " + event.getAction());
        return false;
    }

ViewGroupB中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...onInterceptTouchEvent: false, ACTION = " + ev.getAction());
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupB...onTouchEvent: true, ACTION = " + event.getAction());
        return true;
    }

子View中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...onTouchEvent: false, ACTION = " + event.getAction());
        return false;
    }

可以看出我们只是在ViewGroupB的onTouchEvent()方法中返回了true,即让ViewGroupB来消费这个事件。轻触SonView区域,会得到如下日志结果:

以上日志也可以验证我们上面的结论:第5行ACTION_DOWN事件传递到了子View上,但是第6行子View的onTouchEvent()方法对于这个ACTION_DOWN事件的处理是return了一个false,而第7行父View(即ViewGroupB)的onTouchEvent()方法对于这个ACTION_DOWN事件的处理return了true,后续的ACTION_MOVE、ACTION_UP事件就都传递不到子View上了,而是直接交由ViewGroupB的onTouchEvent()方法处理,并且不再调用ViewGroupB的onInterceptTouchEvent()去判断是否拦截了。

不过这不是我们的目前的重点,重点是onInterceptTouchEvent()方法的返回值,我们修改ViewGroupB中的onInterceptTouchEvent()方法返回true,如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...onInterceptTouchEvent: true, ACTION = " + ev.getAction());
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupB...onTouchEvent: true, ACTION = " + event.getAction());
        return true;
    }

执行后轻触SonView区域,会得到如下日志结果:

如日志所示,第3行当ACTION_DOWN事件到了ViewGroupB上,第4行ViewGroupB需要调用自身的onInterceptTouchEvent()方法判断是否对这个ACTION_DOWN事件进行拦截,此时返回了true表示拦截,那么这个ACTION_DOWN事件就会到ViewGroupB的onTouchEvent()方法中进行响应。第5行ViewGroupB的onTouchEvent()方法也返回了true,那么代表ViewGroupB响应了ACTION_DOWN事件。不过这里有一点不太一样的地方是在于,事件传递到ViewGroupB的onTouchEvent()方法是因为自身的onInterceptTouchEvent()方法判断拦截导致的,而不是由子View回传回来的,在这种情况下,当ACTION_MOVE事件、ACTION_UP事件传递到ViewGroupB的时候,它当然不会传递给子View,并且也不再调用自身的onInterceptTouchEvent()方法了,而是dispatchTouchEvent()->onTouchEvent()的传递直接交由ViewGroupB处理事件。但是,对于ViewGroupA来说,它依然是dispatchTouchEvent()->onInterceptTouchEvent()的流程。

简单来说,就是如果你在onInterceptTouchEvent()方法中执行ACTION_DOWN的时候返回了true,那么该控件后面一系列其它的ACTION就不会再调用onInterceptTouchEvent()方法判断是否拦截了,而是直接调用onTouchEvent()方法消费事件。

猜你喜欢

转载自blog.csdn.net/beita08/article/details/89084033