当我们的手指在Android屏幕上点击或滑动时,就会触发触摸事件TouchEvent。在App中ViewGroup和View存在多级嵌套,在最外层的是Activity,最内层的View,介于Activity与View之间的是一些ViewGroup。本文为了简化讨论,我们假设一个Activity中只有一个ViewGroup,这个ViewGroup中只有一个View。当我们用手指触摸到View的UI时,就会产生触摸事件TouchEvent,总的过程如下图所示:

首先是最外层的Activity接收到该事件,触发Activity的dispatchTouchEvent的执行,在该方法中Activity又会调用内部ViewGroup的dispatchTouchEvent方法的执行,在ViewGroup的dispatchTouchEvent方法中又会调用最内层的View的dispatchTouchEvent方法的执行,在View的dispatchTouchEvent方法中可能会执行View的onTouchEvent方法,然后ViewGroup也有可能执行ViewGroup的onTouchEvent方法,然后Activity也有可能执行Activity的onTouchEvent方法的执行。

上图是精简过的主要流程图,总共是两条主线:

  1. 第一条主线是,从Activity -> ViewGroup -> View,从外向内依次调用dispatchTouchEvent方法,Android会依次把MotionEvent参数传递给该方法。dispatchTouchEvent的作用是传递触摸事件,该主线体现了将触摸事件从外向内逐级传递派发的过程,dispatchTouchEvent是每次传递触摸事件的入口。

  2. 第二条主线是,从View -> ViewGroup -> Activity,从内向外依次调用onTouchEvent方法,Android会依次把MotionEvent参数传递给该方法。onTouchEvent的作用是处理触摸事件,该主线体现了将触摸事件从内向外逐级处理的过程

dispatchTouchEvent和onTouchEvent都接收一个MotionEvent类型的参数,MotionEvent封装了触摸事件的数据信息,包括触摸事件的类型以及坐标位置等,详见博文《Android中的MotionEvent》。dispatchTouchEvent和onTouchEvent都有一个boolean类型的返回值,如果返回true,表示当前对象已经对触摸事件进行了处理;如果返回false,表示当前对象没有对触摸事件进行处理。

下面分别对Activity、ViewGroup、View的事件派发、处理的过程详细说明。


Activity

  • dispatchTouchEvent
    所有在UI上的触摸操作生成的触摸事件都首先会触发Activity中dispatchTouchEvent方法的执行,其源码如下所示:

    public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}

    上述方法的关键是,Activity会首先通过getWindow()方法获取当前的window对象,然后调用window的superDispatchTouchEvent方法,实际上,getWindow()返回的是一个PhoneWindow类型的实例,这样就会调用PhoneWindow的superDispatchTouchEvent方法,其源码如下所示:

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

    mDecor是PhoneWindow中一个DecorView类型的变量,DecorView代表了当前Window最顶级的View,可以看做是根View。由上代码看出,后面会执行DecorView的superDispatchTouchEvent方法,其源码如下所示:

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

    实际上DectorView继承自FrameLayout,所以DectorView间接继承自ViewGroup,所以会DectorView执行其父类ViewGroup对应的dispatchTouchEvent方法。在该方法中,DectorView会找到其触摸的子节点,实际上其子节点也是一个ViewGroup,然后再执行该ViewGroup的dispatchTouchEvent方法,这样就实现了将触摸事件参数MotionEvent从Activity中传入到DecorView的子ViewGroup中了。我们会在后面探讨ViewGroup中的dispatchTouchEvent方法中的执行逻辑,此处就不再过多介绍了。

    以上介绍了借助superDispatchTouchEvent和dispatchTouchEvent方法将触摸事件从Activity到ViewGroup中的传递过程,这两个方法均返回一个boolean类型的参数,如果返回true,表示触摸事件被处理了,反之表示触摸事件没有被处理。我们再看一下上面Activity中dispatchTouchEvent的源码,就会发现如果PhoneWindow的superDispatchTouchEvent返回了true,那么Activity的dispatchTouchEvent方法也就直接返回了true,表明触摸事件被Window给处理了,所以就不会执行后面Activity的 onTouchEvent方法。只有Window没处理触摸事件的情况下,Activity才会调用onTouchEvent方法去处理事件。

  • onTouchEvent
    onTouchEvent的源码如下所示:

    public boolean onTouchEvent(MotionEvent event) {    if (mWindow.shouldCloseOnTouch(this, event)) {        finish();        return true;    }    return false;}

    只有当触摸事件没有被任何的View或ViewGroup处理过的时候,Activity才会执行自己的onTouchEvent去处理触摸事件。一种典型的情形就是,当前触摸点在Window范围之外,这样Window里面所有的View都不会接收更不会处理该触摸事件,这时候我们可以重写该方法实现一些自己的逻辑处理这种情形。如果我们处理了,就返回true,否则返回false。其默认实现基本一直返回false。


ViewGroup

  • dispatchTouchEvent
    当Activity接收到触摸事件之后,会通过DectorView调用ViewGroup的dispatchTouchEvent方法,由于该方法的源码太长,此处就不贴源码了,点此查看其源码。此处主要说一下该方法中的主要逻辑。dispatchTouchEvent方法是ViewGroup对触摸事件进行处理的入口。

    ViewGroup中定义了一个TouchTarget类型的成员变量mFirstTouchTarget,用于保存当前ViewGroup中处理了触摸事件的子View。

    首先,dispatchTouchEvent方法会调用其自身的onInterceptTouchEvent方法,onInterceptTouchEvent是用来拦截ViewGroup将触摸事件传递给其子View的,如果该方法返回true,就表示ViewGroup应该拦截触摸事件;如果返回false,表示ViewGroup不应该拦截触摸事件,应该将触摸事件传递给子View。在dispathTouchEvent方法中还定义了一个boolean类型的handled变量,用于保存dispathTouchEvent方法的返回值,如果是true就表示触摸事件被当前的ViewGroup处理了,反之则表示没被处理。

    然后,只有当onInterceptTouchEvent返回了false,ViewGroup才会依次遍历其子View,其会通过调用isTransformedTouchPointInView方法判断MotionEvent所携带的触摸事件的坐标是否落在子View的范围内,如果触摸事件的坐标恰好落在了该子View范围内,说明我们触摸了当前ViewGroup内的该子View,这样ViewGroup就会把触摸事件的坐标以及该子View传递给dispatchTransformedTouchEvent方法,在该方法内会调用子View的dispatchTouchEvent方法,其返回值表示自View是否处理了触摸事件,如果dispatchTransformedTouchEvent返回true,表示子View处理了触摸事件,这样ViewGroup会通过调用addTouchTarget方法将mFirstTouchTarget绑定该子View,并且变量alreadyDispatchedToNewTouchTarget也会设置为true,表示已经有子View处理了触摸事件。一旦有子View处理了触摸事件,ViewGroup就会通过break跳出for循环,不再对其他子View进行遍历。

    在经过了对子View的for循环之后,如果没有任何的子View处理了触摸事件,那么mFirstTouchTarget就还是null,此时ViewGroup就会将null作为child参数传入dispatchTransformedTouchEvent方法中,该方法会调用super.dispatchTouchEvent方法,由于ViewGroup继承自View,以此处就相当于执行了View类中的dispatchTouchEvent方法,这样就很有可能执行ViewGroup从View中继承来的onTouchEvent方法。dispatchTransformedTouchEvent的返回值会作为局部变量handled的值。关于View类中的dispatchTouchEvent方法会在下面详细说明。

    在经过了对子View的for循环之后,如果发现某个子View对触摸事件进行了处理,那么alreadyDispatchedToNewTouchTarget就是true,从而会将局部变量handled设置为true,即表示只要有子View处理了触摸事件,就表示当前的ViewGroup也处理了触摸事件,并且这种情况下ViewGroup不会调用从View中继承来的dispatchTouchEvent方法,从而不会触发ViewGroup的onTouchEvent方法的执行。

  • onInterceptTouchEvent
    之前提到过onInterceptTouchEvent用于拦截ViewGroup向子View传递触摸事件,ViewGroup中的默认实现一直返回false,即表示不拦截。我们可以重写该方法以实现我们自己的触摸事件拦截逻辑。

  • dispatchTransformedTouchEvent
    点此查看源码,其主要的逻辑代码如下所示:

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {  final boolean handled;  final MotionEvent transformedEvent;  ......            // Perform any necessary transformations and dispatch.    if (child == null) {        handled = super.dispatchTouchEvent(transformedEvent);    } else {        final float offsetX = mScrollX - child.mLeft;        final float offsetY = mScrollY - child.mTop;        transformedEvent.offsetLocation(offsetX, offsetY);        if (! child.hasIdentityMatrix()) {            transformedEvent.transform(child.getInverseMatrix());        }        handled = child.dispatchTouchEvent(transformedEvent);    }    // Done.    transformedEvent.recycle();    return handled;}

    该方法的主要目的是将MotionEvent中的x、y的坐标转换成所传入的child变量所指定的的View的坐标系中的坐标,transformedEvent表示了已经完成了指定坐标系转换的MotionEvent。如果传入的child参数是null,表示传入的是当前的ViewGroup,此时就将直接调用super.dispatchTouchEvent(transformedEvent),这样就让ViewGroup调用了父类View中的dispatchTouchEvent方法;如果传入的child参数不是null,表示传入的当前ViewGroup的一个子View,那么就会调用child.dispatchTouchEvent(transformedEvent),从而将触摸事件从ViewGroup传递到子View中去。我们会在下面介绍View的dispatchTouchEvent的实现逻辑。

  • onTouchEvent
    ViewGroup的onTouchEvent继承自View的onTouchEvent方法,ViewGroup并没有重写,我们在下面会介绍View的onTouchEvent方法的实现逻辑。


View

  • dispatchTouchEvent
    点此查看源码,其源码的主要逻辑如下所示:

    public boolean dispatchTouchEvent(MotionEvent event) {    ......    boolean result = false;    ......    if (onFilterTouchEventForSecurity(event)) {        //noinspection SimplifiableIfStatement        ListenerInfo li = mListenerInfo;        //如果设置了OnTouchListener,那么会在此处执行OnTouchListener的onTouch方法        if (li != null && li.mOnTouchListener != null                && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {            //如果OnTouchListener的onTouch方法返回true,就表示触摸事件被处理了,result就会设置为true            result = true;        }        //如果触摸事件没有被OnTouchListener处理,那么就会执行View的onTouchEvent方法        if (!result && onTouchEvent(event)) {            //如果onTouchEvent返回了true,就表示触摸事件被View处理了,result就被设置为了true            result = true;        }    }    ......    return result;}

    dispatchTouchEvent是View处理触摸事件的入口。在该方法中,View首先会查看其有没有设置过OnTouchListener,如果设置过就调用OnTouchListener的onTouch方法,如果其返回了true,就表明触摸事件被处理了,result就会设置为true。如果触摸事件没有被OnTouchListener处理,那么就会执行View的onTouchEvent方法,如果onTouchEvent返回了true,就表示触摸事件被View处理了,result就被设置为了true。

    由上可以看出,在dispatchTouchEvent方法中是先执行OnTouchListener的onTouch方法,一旦其返回true,就不会调用View自身的onTouchEvent方法了,只有OnTouchListener没有处理触摸事件才会在后面执行View的onTouchEvent方法。

  • onTouchEvent
    点此查看源码,View.onTouchEvent()方法中,如果View注册了CLICK或LONG_CLICK等事件监听器,那么就会让注册的事件监听器处理触摸事件,这样onTouchEvent就返回true。会根据ACTION的不同,执行不同的处理,比如如果是ACTION_UP,会执行performClick()方法,该方法会触发OnClickListener.onClick()的执行。
    如果View没有注册任何的CLICK或LONG_CLICK等的事件监听器,那么onTouchEvent就返回false,表示onTouchEvent没有对传入的触摸事件MotionEvent做任何处理。


总结

我们通过对上面Activity、ViewGroup、View各个层级对触摸事件的处理过程可以发现,Android中每个层级对触摸事件的处理都是从dispatchTouchEvent方法开始的,首先先调用下一层级的dispatchTouchEvent方法,将触摸事件传递给下一层级,如果下一层级对触摸事件进行了处理,就可认为本层级也对触摸事件进行了处理,那么本层级就不会对触摸事件仅需做其他特殊处理了;如果下一层级没有对触摸事件进行处理,即下一层级的dispatchTouchEvent方法返回false,那么才会调用本层级的onTouchEvent方法对触摸事件进行处理。

我的更多博文可参见《我的Android博文整理汇总》,希望本文对大家理解Android中的触摸事件机制有所帮助!

更多相关文章

  1. Android(安卓)事件分发
  2. Android(安卓)开发之拦截EditText的输入内容,定制输入内容
  3. android 识别U盘以及读写文件的方法
  4. Android仿小米商城底部导航栏(基于BottomNavigationBar)
  5. 从源码角度分析Activity、Window和DecorView的关系
  6. Android四大组之ContentProvider
  7. Android(安卓)Dependencies小差号引起的问题
  8. [转]Android读写XML(中)——SAX
  9. Android(安卓)Studio下三种包名的修改方法

随机推荐

  1. 谈谈对Linux的Huge Pages与Transparent H
  2. 华为外包程序员跳楼,难道这是我的35岁?
  3. 74.格式化xml脚本
  4. 虚拟机VBox安装CentOS6.8,内外网访问
  5. 接口限流的四种算法
  6. 分布式事务一致性
  7. 阿里 P8 病逝,自如甲醛房是元凶?
  8. Kafka为什么速度快
  9. 别扯了,学Flutter,Android 就不会凉?
  10. 我又一次被 Google Developer Day 给丑拒