Android中TouchEvent触摸事件机制

当我们的手指在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方法,其源码如下所示:

    @Override
    public 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中的触摸事件机制有所帮助!

  • 14
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值