Android 触摸事件分发和处理机制解析(一)Activity篇

在我们自定义view或者嵌套view时,经常需要处理滑动事件,点击事件等各种交互事件。在处理过程中,我们可能会遇到事件不响应,滑动和点击事件冲突等问题,这时候,如果我们理解Android触摸事件的分发和处理,处理起来就会得心应手。

刚开始接触Android触摸事件分发和处理机制的时候,往往会一头雾水,因为处理触摸事件的地方太多了。

比如,我们可以对某个activity里的view设置onTouchListenter(),也可以在activity里重载onTouchEvent()方法,那么到底是谁会处理触摸事件呢?
又比如,activity里可以重载dispatchTouchEvent()方法,而自定义layout里也可以重载dispatchTouchEvent()方法,那么在activity里用到自定义layout的时候,应该用哪个方法处理触摸事件的分发呢?

一、开始之前,先记住下面触摸事件涉及的三个重要方法:

public boolean dispatchTouchEvent(MotionEvent ev)   //触摸事件分发
public boolean onInterceptTouchEvent(MotionEvent event)     //触摸事件拦截
public boolean onTouchEvent(MotionEvent event)      //触摸事件处理

ViewGroup包含所有这三个方法。如果我们要自定义ViewGroup(比如常见的FrameLayout,ListView等都继承自ViewGroup),则可以有选择地重载这三个方法。

Activity和View不包含第二个onInterceptTouchEvent(MotionEvent event)即事件拦截方法,包含其它两个。
所以在我们创建的activity和自定义View(比如自定义Button等)中,可以有选择地重载dispatchTouchEvent(MotionEvent event) 和 onTouchEvent(MotionEvent event)。

看到这可能大家会有疑问,不是还有我们经常用到的setOnTouchListener()以及OnTouchListener接口里的onTouch()方法吗?怎么没提?
——这些方法,放到《Android 触摸事件分发和处理机制解析(三)——View篇》里会讲。

那么既然Activity,ViewGroup,View里都有事件处理相关方法,那么发生触摸事件时,事件处理的先后顺序是怎样的呢?

二、记住第二条:触摸事件是从Activity的dispatchTouchEvent()开始处理的。

对新手来说,记住就好,本文不做深究。以此为前提,我们来看Activity类的dispatchTouchEvent()的源码。

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }   
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

————————————————————分割线———————————————————————-

三、在分析源码之前,先记住第三条,触摸事件的组成:

一般来说,一个完整的触摸事件包括按下,滑动,抬起三步。按下,滑动,抬起对应的MotionEvent的action分别为MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE 和 MotionEvent.ACTION_UP。
当我们的手指对屏幕进行操作时,系统会把我们的操作封装成MotionEvent对象,传入触摸事件的处理方法中。

因此,当我们点一下屏幕,Activity的dispatchTouchEvent()就会收到若干个MotionEvent事件。具体顺序是,先收到按下事件,处理完成后,再收到滑动事件(0个或多个)依次处理完成,最后传入一个抬起事件。

————————————————————分割线———————————————————————-

好,知道了这些,再一步一步看上面Activity的dispatchTouchEvent()的源码:

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }

如果传入的是按下的事件,则调用onUserInteraction()。这个可以不用管,onUserInteraction()在Activity类里是个空方法,是让我们重载以处理各种设备交互的。
往下看:

        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);

这两步是重点,在这里我们看到了一个熟悉的身影,即Activity的onTouchEvent()方法。因此看到这里,我们刚开始说要记住的,Activity的关于触摸事件的两个方法都现身了,而且onTouchEvent()被调用的地方就这一处。

可以得出:假如getWindow().superDispatchTouchEvent(ev)返回true的话,activity的onTouchEvent()就不会执行了,否则,dispatchTouchEvent()返回的就是onTouchEvent()的返回值。

那么,getWindow().superDispatchTouchEvent(ev)是怎么处理的呢?
点源码,getWindow()方法返回一个Window对象,而Window是个抽象类,其唯一实现类是PhoneWindow类。看下PhoneWindow的superDispatchTouchEvent()方法:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

它调用了DecorView的superDispatchTouchEvent()方法进行处理。继续看DecorView的superDispatchTouchEvent()方法:

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

又扔给父类处理了,因为DecorView继承自FrameLayout,而FrameLayout里没有重写此方法,最后还是扔给了FrameLayout的父类ViewGroup,所以最终又走到了ViewGroup的dispatchTouchEvent()方法。这是之前提到的ViewGroup的三个重要方法之一。

看到这儿,我们先不管ViewGroup的dispatchTouchEvent()了。来梳理一下:
Activity的onTouchEvent()方法会不会执行,取决于getWindow().superDispatchTouchEvent()的返回值。

而后者最后又调用了ViewGroup的dispatchTouchEvent()作为返回值。所以,如果ViewGroup(这个ViewGroup就是DecorView,是页面的根视图)的dispatchTouchEvent()返回false,则Activity的onTouchEvent()就会执行,否则就得不到执行。

看下Activity的onTouchEvent()源码:

    /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

很简单,返回true表示触摸事件被消费了,不用别人再处理;返回false表示没有被消费。默认情况下返回false。

本篇学习的东西:


1. 触摸事件是从Activity的 dispatchTouchEvent() 开始处理的。

2. Activity的dispatchTouchEvent() 中调用了它的 onTouchEvent(),这也是它的onTouchEvent()唯一的调用 。

3. Activity的dispatchTouchEvent()又间接调用了该Activity的根ViewGroup的dispatchTouchEvent()。onTouchEvent()会不会被执行,取决于后者的返回值。如果它返回true,则Activity的onTouchEvent() 就不会执行了。

————————————————————分割线———————————————————————-

扩展问题:如果我们创建了一个activity,像下面这样重写它的两个方法,运行打印会怎样呢?

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent() called with: event = [" + event + "]");
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "dispatchTouchEvent() called with: ev = [" + ev + "]");
        return true;    //改为return false打印结果是一样的
    }

运行之后,触摸屏幕,会不断打出dispatchTouchEvent的Log,但是不会打onTouchEvent的Log,因为onTouchEvent得不到执行了。这样就拦截了这个页面的所有触摸事件(包括点击事件)。

上面我们从Activity中追踪到了ViewGroup的dispatchTouchEvent()方法,那下一步,我们就来分析ViewGroup的触摸事件分发和处理:
Android 触摸事件分发和处理机制解析(二)ViewGroup篇

猜你喜欢

转载自blog.csdn.net/fenggering/article/details/79525832