Android事件分发机制详解

本文深入剖析Android中事件分发机制,包括事件分发的对象、分发阶段及各组件如何处理点击事件,并通过源码解析Activity、ViewGroup、View的事件分发机制。

本文是在我阅读相关资料文献做出的心得总结,欢迎批评指正!

0.引言

在Android开发中我们经常遇到多个View、ViewGroup嵌套的问题,例如ViewPager中嵌套Fragment,而在Fragment中需要实现一个横向滚动的广告栏,这时候就会遇到广告栏的滑动事件和ViewPager的滑动事件相互冲突的问题,想要正确解决这种问题,我们就需要对android中事件分发机制要有较深入的理解。

1.事件分发的对象

  • Android事件分发机制分发传递对象是什么?–事件
    当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象

  • Touch事件有如下四种
    ACTION_DOWN:按下View(所有事件的开始)
    ACTION_MOVE:滑动View
    ACTION_CANCEL:非人为原因结束本次事件(比如当手指在手机屏幕上移动时,突然一个电话打进来)
    ACTION_UP:抬起View(与DOWN对应)

  • 事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:

MotionEvent事件列

即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理。

2. 事件分发通过的对象

Android中事件分发通过的对象为Activity、ViewGroup、View

  • Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成,它们在屏幕上的主要显示关系如下图所示:

    Activity、ViewGroup、View三者屏幕显示关系

    即两种屏幕显示关系:

1、Activity里面放ViewGroup,ViewGroup里放View(当然ViewGroup里还可以放ViewGroup,这里我们针对事件分发所以简化处理)
2、Activity中直接放View

View是所有UI组件的基类

一般Button、ImageView、TextView等控件都是继承父类View

ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup)

其本身也是从View派生的,即ViewGroup是View的子类是Android所有布局的父类或间接父类:像我们用到的布局(LinearLayout、RelativeLayout、FrameLayout等),都继承自ViewGroup,即属于ViewGroup子类。与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能
Activity的用户界面是由层级式视图衍生自 View 类的对象提供的。其显示关系如下图所示:

activity层级显示关系

PhoneWindow是Android系统中最基本的窗口系统,每一个Activity会创建一个PhoneWindow,PhoneWindow是Activity和View系统交互的接口。DecorView本质上其实就是一个FrameLayout,是Activity中所有View的祖先。从这里我们可以知道上面所说的Activity里放ViewGroup或View其实就是放在上图中的ContentView上的。

3. 事件分发的三个阶段

3.1 Android事件分发流程

Activity(Window) -> ViewGroup -> View

即秉持一个原则–“从外层到里层原则”,该过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成。

- 分发(Dispatch)

事件的分发对应着dispatchTouchEvent方法,在Android系统中,所有的触摸事件都是通过这个方法来分发,方法原型如下:

`public boolean dispatchTouchEvent(MotionEvent ev)`

作用:分发点击事件

调用时刻:当点击事件能够传递给当前View时,该方法就会被调用

返回结果:根据当前的对象不同返回方法不同

默认情况
Activity:super.dispatchTouchEvent(),即调用父类ViewGroup的dispatchTouchEvent() (注意:为什么在Activity会调用ViewGroup的dispatchTouchEvent?下面在源码部分分析阶段会给予解释);
ViewGroup:onInterceptTouchEvent(),即调用自身的onInterceptTouchEvent();
View:onTouchEvent(),即调用自身的onTouchEvent();
true
消费事件,且事件不会往下传递;后续事件会继续分发到该View;
false:
不消费事件,且事件停止传递;将事件回传给父控件的onTouchEvent()处理;当前View仍然接受此事件的其他事件

- 拦截(Intercept)

事件的拦截对应着onInterceptTouchEvent方法,这个方法只有在ViewGroup及其子类才会存在,在View和Activity中是不存在的。方法原型如下:

`public boolean onInterceptTouchEvent(MotionEvent ev)`

作用:判断是否拦截了某个事件

调用时刻:在ViewGroup的dispatchTouchEvent()内部调用;

返回结果

默认情况:
返回super.onInterceptTouchEvent(),不拦截事件且事件继续往下传递;当前View仍然接受此事件的其他事件;事件传递到子View,调用父类View.dispatchTouchEvent方法去处理
true:
拦截事件,且事件不会往下传递;调用自己的onTouchEvent()处理;同一事件列其他事件直接交由该View处理;并且在同意事件列中该方法不会再次被调用。
false:
情况同默认情况

- 消费(Consume)

事件的消费对应着onTouchEvent方法,方法原型如下:

  `public boolean onTouchEvent(MotionEvent ev)`

作用:处理点击事件

调用时刻:在dispatchTouchEvent()内部调用;

返回结果:是否消费该事件

true:
自己处理事件,且事件不会往下传递;后续事件列会让其处理;
false
不处理事件;将事件向上传递给父控件的onTouchEvent()处理;当前事件不会接受此事件的其他事件;

3.2 三者之间的关系和传递规则通过伪代码阐述

  // 点击事件产生后,会直接调用dispatchTouchEvent()方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //代表是否消耗事件
        boolean consume = false; 
        if (onInterceptTouchEvent(ev)) {
        //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
        //则该点击事件则会交给当前View进行处理
        //即调用onTouchEvent ()方法去处理点击事件
          consume = onTouchEvent (ev) ;
        } else {
          //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
          //则该点击事件则会继续传递给它的子元素
          //子元素的dispatchTouchEvent()就会被调用,重复上述过程
          //直到点击事件被最终处理为止
          consume = child.dispatchTouchEvent (ev) ;
        }
        return consume;
    }

3.3 通过一幅流程图对事件分发进行说明

事件分发流程图

4 事件分发场景介绍

事件分发案例

如上图所示

  • 最外层:Activiy A,包含两个子View:ViewGroup B、View C
  • 中间层:ViewGroup B,包含一个子View:View C
  • 最内层:View C

    假设用户首先触摸到屏幕上View C上的某个点,那么Action_DOWN事件就在该点产生,然后用户移动手指并最后离开屏幕。

4.1默认情况

即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值那么调用的是这3个方法的默认实现:调用父类的方法

事件传递情况:

从Activity A—->ViewGroup B—>View C,从上往下调用dispatchTouchEvent()
再由View C—>ViewGroup B —>Activity A,从下往上调用onTouchEvent()

注:虽然ViewGroup B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent() 这一点与onTouchEvent的行为是不一样的。

4.2处理事件

假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。
最常见的:设置Button按钮来响应点击事件

事件传递情况:

DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件,因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()

4.3拦截事件

假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。

事件传递情况:

DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。

4.4拦截后续事件(Move、UP)事件

假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。

DOWN事件传递到C的onTouchEvent方法,返回了true。
在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
后续事件将直接传递给B的onTouchEvent()处理
后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
C再也不会收到该事件列产生的后续事件。

特别注意

如果ViewGroup 拦截了一个半路的事件(如MOVE),这个事件将会被系统变成一个CANCEL事件并传递给之前处理该事件的子View;该事件不会再传递给ViewGroup 的onTouchEvent()只有再到来的事件才会传递到ViewGroup 的onTouchEvent()

5 Android事件分发机制源码分析

Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
所以,要想充分理解Android分发机制,本质上是要理解:

Activity对点击事件的分发机制
ViewGroup对点击事件的分发机制
View对点击事件的分发机制

接下来,我将通过源码分析详细介绍Activity、View和ViewGroup的事件分发机制

5.1 Activity的事件分发机制

  • 当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,具体是由Activity的Window来实现
  • Activity的dispatchTouchEvent()的源码
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //关注点1
        //一般事件列开始都是DOWN,所以这里基本是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //关注点2
            onUserInteraction();
        }
        //关注点3
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

关注点1

一般事件列开始都是DOWN(按下按钮),所以这里返回true,执行onUserInteraction()

关注点2

先来看下onUserInteraction()源码

   /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
    public void onUserInteraction() { 
    }

从源码可以看出:

该方法为空方法
从注释得知:当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
所以onUserInteraction()主要用于屏保

关注点3

Window类是抽象类,且PhoneWindow是Window类的唯一实现类
superDispatchTouchEvent(ev)是抽象方法,返回的是一个Window对象
通过PhoneWindow类中看一下superDispatchTouchEvent()的作用

PhoneWindow中superDispatchTouchEvent方法源码如下:

 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
       //mDecor是DecorView的实例
       //DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
       return mDecor.superDispatchTouchEvent(event);
    }

接下来我们看mDecor.superDispatchTouchEvent(event)源码:

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
        //DecorView继承自FrameLayout
        //那么它的父类就是ViewGroup
        而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
    }

所以,执行Activity中getWindow().superDispatchTouchEvent(ev)实际上是执行了ViewGroup.dispatchTouchEvent(event)

总之Activity中当一个点击事件发生时,调用顺序如下:

1、事件最先传到Activity的dispatchTouchEvent()进行事件分发
2、调用Activity中dispatchTouchEvent()方法里getWindow().superDispatchTouchEvent(ev)
3、由于getWindow()返回的是抽象Window类,因此getWindow().superDispatchTouchEvent(ev)调用Window类实现类PhoneWindow的superDispatchTouchEvent()
4、在PhoneWindow的superDispatchTouchEvent()里调用DecorView的superDispatchTouchEvent()
5、在DecorView的superDispatchTouchEvent()里调用super.dispatchTouchEvent(event),即调用DecorView父类的dispatchTouchEvent(),而DecorView继承自FrameLayout,但是FrameLayout又继承自ViewGroup,因此最终调用的是ViewGroup的dispatchTouchEvent()方法

如果ViewGroup的dispatchTouchEvent()返回true就不执行Activity的onTouchEvent()方法;如果返回false,就执行。

5.2 ViewGroup事件的分发机制

从上面的测试结果发现:

当点击Button时,执行Button的onClick(),但ViewGroupLayout注册的onTouch()不会执行只有点击空白区域时才会执行ViewGroupLayout的onTouch();结论:Button的onClick()将事件消费掉了,因此事件不会再继续向下传递。

1、ViewGroup的dispatchTouchEvent()源码(采用android5.0之前,Android 5.0后ViewGroup的dispatchTouchEvent()的源码发生了变化(更加复杂),但原理相同)如下:

 public boolean dispatchTouchEvent(MotionEvent ev) {  
        final int action = ev.getAction();  
        final float xf = ev.getX();  
        final float yf = ev.getY();  
        final float scrolledXFloat = xf + mScrollX;  
        final float scrolledYFloat = yf + mScrollY;  
        final Rect frame = mTempRect;  
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
        if (action == MotionEvent.ACTION_DOWN) {  
            if (mMotionTarget != null) {  
                mMotionTarget = null;  
            }  
            //看这个If判断语句
            //第一个判断值disallowIntercept:是否禁用事件拦截的功能(默认是false)
            //可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。
            //第二个判断值: !onInterceptTouchEvent(ev):对onInterceptTouchEvent()返回值取反
            //如果我们在onInterceptTouchEvent()中返回false,就会让第二个值为true,从而进入到条件判断的内部
            //如果我们在onInterceptTouchEvent()中返回true,就会让第二个值为false,从而跳出了这个条件判断。
            //关于onInterceptTouchEvent()请看下面分析(关注点1)
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
                ev.setAction(MotionEvent.ACTION_DOWN);  
                final int scrolledXInt = (int) scrolledXFloat;  
                final int scrolledYInt = (int) scrolledYFloat;  
                final View[] children = mChildren;  
                final int count = mChildrenCount;  

                //通过for循环,遍历了当前ViewGroup下的所有子View
                for (int i = count - 1; i >= 0; i--) {  
                    final View child = children[i];  
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                        child.getHitRect(frame);  
                        //判断当前遍历的View是不是正在点击的View
                        //如果是,则进入条件判断内部
                        if (frame.contains(scrolledXInt, scrolledYInt)) {  
                            final float xc = scrolledXFloat - child.mLeft;  
                            final float yc = scrolledYFloat - child.mTop;  
                            ev.setLocation(xc, yc);  
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                            //关注点2
                            //条件判断的内部调用了该View的dispatchTouchEvent()方法(具体请看下面的View事件分发机制)
                            //实现了点击事件从ViewGroup到View的传递
                            if (child.dispatchTouchEvent(ev))  { 
                                //调用子View的dispatchTouchEvent后是有返回值的
                                //如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true
                                //因此会导致条件判断成立
                                mMotionTarget = child;  
                                //于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出
                                //即把ViewGroup的touch事件拦截掉
                                return true;  
                            }  
                        }  
                    }  
                }  
             }  
        }  

        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||(action == MotionEvent.ACTION_CANCEL);  
        if (isUpOrCancel) {  
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
        }  
        final View target = mMotionTarget;  

        //关注点3
        //没有任何View接收事件的情况,即点击空白处情况
        if (target == null) {  
            ev.setLocation(xf, yf);  
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
                ev.setAction(MotionEvent.ACTION_CANCEL);  
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            }  
            //调用ViewGroup的父类View的dispatchTouchEvent()
            //因此会执行ViewGroup的onTouch()、onTouchEvent()
            //实现了点击事件从ViewGroup到View的传递
            return super.dispatchTouchEvent(ev);  
        }  

        //之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
            final float xc = scrolledXFloat - (float) target.mLeft;  
            final float yc = scrolledYFloat - (float) target.mTop;  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            ev.setLocation(xc, yc);  
            if (!target.dispatchTouchEvent(ev)) {  
            }  
            mMotionTarget = null;  
            return true;  
        }  
        if (isUpOrCancel) {  
            mMotionTarget = null;  
        }  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        ev.setLocation(xc, yc);  
        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            mMotionTarget = null;  
        }  
        return target.dispatchTouchEvent(ev);  
    }  

关注点1(onInterceptTouchEvent()源码分析)

ViewGroup每次在做分发时,需要调用onInterceptTouchEvent()是否拦截事件;源码分析如下:

  public boolean onInterceptTouchEvent(MotionEvent ev) {  
      return false;  
    } 

返回false =不拦截(默认),允许事件继续往下传递(向子View传递);
因为子View也需要该事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,不会拦截
返回true = 拦截(手动设置),即自己处理该事件(执行自己的onTouchEvent()),事件不会继续往下传递

关注点2

当点击了某个控件:

调用该控件所在布局(ViewGroup)的dispatchTouchEvent() 在布局的dispatchTouchEvent()中找到被点击的相应子控件再去调用该控件的dispatchTouchEvent()
1、实现了点击事件从ViewGroup到View的传递
2、此处是关于View.dispatchTouchEvent()的分析,详情请看下面的View事件分发机制。

结论

Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View,在ViewGroup中通过onInterceptTouchEvent()对事件传递进行拦截

  • 1、onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;
  • 2、返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)
  • 3、子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。

由于ViewGroup自身并没有实现onTouchEvent方法而是通过继承父类View拥有这一功能,所以ViewGroup的onTouchEvent源码分析可以参考View的onTouchEvent()方法

5.3 View事件的分发机制

1、View中dispatchTouchEvent()的源码如下:

  public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
            return true;  
         }  
         return onTouchEvent(event);  
    }  

从上面可以看出只有以下三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法

  • 第一个条件:mOnTouchListener != null;
  • 第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
  • 第三个条件:mOnTouchListener.onTouch(this, event);

第一个条件:mOnTouchListener!= null

    //mOnTouchListener是在View类下setOnTouchListener方法里赋值的
    public void setOnTouchListener(OnTouchListener l) { 

        //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
        mOnTouchListener = l;  
    }  

第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED

该条件是判断当前点击的控件是否enable由于很多View默认是enable的,因此该条件恒定为true

第三个条件:mOnTouchListener.onTouch(this, event)

    回调控件注册Touch事件时的onTouch方法
    //手动调用设置
    button.setOnTouchListener(new OnTouchListener() {  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  

            return false;  
        }  
    });

如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。

如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

2、View的onTouchEvent(event)的源码分析

    public boolean onTouchEvent(MotionEvent event) {  
        final int viewFlags = mViewFlags;  
        if ((viewFlags & ENABLED_MASK) == DISABLED) {  
            // A disabled view that is clickable still consumes the touch  
            // events, it just doesn't respond to them.  
            return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
        }  
        if (mTouchDelegate != null) {  
            if (mTouchDelegate.onTouchEvent(event)) {  
                return true;  
            }  
        }  
        //如果该控件是可以点击的就会进入到下两行的switch判断中去;

        if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
            //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
            switch (event.getAction()) {  
                case MotionEvent.ACTION_UP:  
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                   // 在经过种种判断之后,会执行到关注点1的performClick()方法。
                   //请往下看关注点1
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                        // take focus if we don't have it already and we should in  
                        // touch mode.  
                        boolean focusTaken = false;  
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                            focusTaken = requestFocus();  
                        }  
                        if (!mHasPerformedLongPress) {  
                            // This is a tap, so remove the longpress check  
                            removeLongPressCallback();  
                            // Only perform take click actions if we were in the pressed state  
                            if (!focusTaken) {  
                                // Use a Runnable and post this rather than calling  
                                // performClick directly. This lets other visual state  
                                // of the view update before click actions start.  
                                if (mPerformClick == null) {  
                                    mPerformClick = new PerformClick();  
                                }  
                                if (!post(mPerformClick)) {  
                                    //关注点1
                                    //请往下看performClick()的源码分析
                                    performClick();  
                                }  
                            }  
                        }  
                        if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                        }  
                        if (prepressed) {  
                            mPrivateFlags |= PRESSED;  
                            refreshDrawableState();  
                            postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());  
                        } else if (!post(mUnsetPressedState)) {  
                            // If the post failed, unpress right now  
                            mUnsetPressedState.run();  
                        }  
                        removeTapCallback();  
                    }  
                   break;  
                case MotionEvent.ACTION_DOWN:  
                    if (mPendingCheckForTap == null) {  
                        mPendingCheckForTap = new CheckForTap();  
                    }  
                    mPrivateFlags |= PREPRESSED;  
                    mHasPerformedLongPress = false;  
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                  break;  
                case MotionEvent.ACTION_CANCEL:  
                    mPrivateFlags &= ~PRESSED;  
                    refreshDrawableState();  
                    removeTapCallback();  
                  break;  
                case MotionEvent.ACTION_MOVE:  
                    final int x = (int) event.getX();  
                    final int y = (int) event.getY();  
                    // Be lenient about moving outside of buttons  
                    int slop = mTouchSlop;  
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                        // Outside button  
                        removeTapCallback();  
                        if ((mPrivateFlags & PRESSED) != 0) {  
                            // Remove any future long press/tap checks  
                            removeLongPressCallback();  
                            // Need to switch from pressed to not pressed  
                            mPrivateFlags &= ~PRESSED;  
                            refreshDrawableState();  
                        }  
                    }  
                  break;  
        }  
        //如果该控件是可以点击的,就一定会返回true
        return true;  
      }  
    //如果该控件是不可以点击的,就一定会返回false
     return false;  
    }

关注点1:
performClick()的源码分析

 public boolean performClick() {  
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  

        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
        }  
        return false;  
    }  

只要mOnClickListener不为null,就会去调用onClick方法;

那么,mOnClickListener又是在哪里赋值的呢?请继续看:

  public void setOnClickListener(OnClickListener l) {  
        if (!isClickable()) {  
            setClickable(true);  
        }  
        mOnClickListener = l;  
    } 

当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。

结论

onTouch()的执行高于onClick()
每当控件被点击时:

  • 1、如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。
    即:onTouch()返回false(该事件没被onTouch()消费掉)–> dispatchTouchEvent()返回false(继续向下传递) –> 执行onTouchEvent() –> 执行OnClick()

  • 2、如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;
    即:onTouch()返回true(该事件被onTouch()消费掉) –> dispatchTouchEvent()返回true(不会再继续向下传递)–> 不会执行onTouchEvent() –> 不会执行OnClick()

这里给出View层事件传递的详细过程图:

View层事件分发详解

6 拓展

6.1 onTouch()和onTouchEvent()的区别

这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。

特别注意:请看下面代码

 //&&为短路与,即如果前面条件为false,将不再往下执行
    //所以,onTouch能够得到执行需要两个前提条件:
    //1. mOnTouchListener的值不能为空
    //2. 当前点击的控件必须是enable的。
    mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)

因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类非enable控件,如果我们想要监听它的touch事件,就必须通过在该控件中直接重写onTouchEvent方法来实现。

6.2 onTouch()和onClick()的区别

1、onClick就传入一个View对象,而 onTouch要传入一个View 对象和 MotionEvent的对象

2、onTouch对控件的操作比onCilck更丰富,比如判断触摸的状态(比如按下,或者放开),和得到点击的位置等等,onClick只有当触发UP时候才触发该方法

3、当对一个控件触摸的时候onTouch 先调用,onclick是对onTouch()的一个扩展实现

4、onClick方法里并没有返回布尔值,而onTouch方法是有返回布尔型值的

5、onClick方法是在View的onTouchEvent方法里触发ACTION_UP事件调用performClick()里调用onClickListener回调方法;onTouch方法是在View的dispatchTouchEvent方法中通过调用OnTouchListener中回调方法onTouch判断当前事件是否需要继续分发。

6.3 Touch事件的后续事件(MOVE、UP)层级传递

如果给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)

即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE和ACTION_UP事件都不会执行

从上面对事件分发机制分析知:

dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用

请记住:接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)

这里给出ACTION_MOVE和ACTION_UP事件的传递结论:

如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()消费事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP

黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE和ACTION_UP事件传递方向

Touch事件的后续事件1 Touch事件的后续事件2

如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费事件(返回true),那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent()并结束本次事件传递过程。

黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE和ACTION_UP事件传递方向

Touch事件的后续事件3 Touch事件的后续事件4

到此事件分发机制就阐述完了!

本文相关Demo代码

参考:https://www.jianshu.com/p/38015afcdb58

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值