事件分发机制

基础知识

(1) 所有Touch事件都被封装成了MotionEvent对象,包括Touch的位置、时间、历史记录以及第几个手指(多指触摸)等。

(2) 事件类型分为ACTION_DOWN,ACTION _UP, ACTION_MOVE,ACTION _POINTER_DOWN,ACTION _POINTER_UP, ACTION_CANCEL,

     每个事件都是以ACTION_DOWN开始ACTION_UP结束。

(3) 3个主要方法:

 dispathcTouchEvent()        分发、传递事件

 onInterceptTouchEvent()   拦截事件

 onTouchEvent()                   处理事件

      

其中onInterceptTouchEvent()是ViewGroup独有方法;

Activity、View、ViewGroup都有另外两个方法,不过源码中dispathcTouchEvent里执行的内容各有不同。


一般来说,Android的事件分发机制可以简单地理解为:

   Activity.dispatchTouchEvent --> ViewGroup.dispatchTouchEvent --> View.dispatchEvent

   其中,Activity.dispatchTouchEvent负责找到最外层ViewGroup;然后用这ViewGroup.dispatchTouchEvent来遍历子view,调用子view的dispatchEvent,处理子view的onTouch、onTouchEvent、onClick


Activity.dispatchTouchEvent()方法源码

我们来看Activity类dispatchTouchEvent()

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//空方法,忽略吧
        }
        /**
        * getWindow()获取到当前Window对象,表示顶层窗口,管理界面的显示和事件的响应;
        * Window类是一个抽象类,只有一个子类PhoneWindow;
        * 每个Activity均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口
        */
        if (getWindow().superDispatchTouchEvent(ev)) { //开始一层一层往下遍历view的dispatchTouchEvent,看哪个返回了true
            return true;
        }
        return onTouchEvent(ev);//所有子view都返回false,就调用自身的onTouchEvent(没复写的话,默认调用Activity.onTouchEvent)
    }
所以我们找到PhoneWindow类,查看它的superDispatchTouchEvent()方法。

   @Override
   public boolean superDispatchTouchEvent(MotionEvent event) {
       return mDecor.superDispatchTouchEvent(event);//mDecor就是当前窗口最顶层的DecorView
   }

DecorView是整个Window界面的最顶层ViewGroup,是FrameLayout的子类,接着看DecorView中的superDispatchTouchEvent()方法

   public boolean superDispatchTouchEvent(MotionEvent event) {
       return super.dispatchTouchEvent(event);
   }
调用了super.dispatchTouchEveent(),而DecorView的父类是FrameLayout所以我们找到FrameLayout.dispatchTouchEveent().

我们看到FrameLayout中没有重写dispatchTouchEveent()方法,所以可以肯定它调用的就是FrameLayout的父类ViewGroupdispatchTouchEvent()。

绕来绕去,Activity.dispatchTouchEvent()其实就是在寻找最外层ViewGroup,让它来给下面的view分发事件。

至此,我们可以试着理解下面这张图了(不理解很正常,接着看完后面内容再回来看图吧)

       事件分发机制_第1张图片
事件下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件



View.dispatchTouchEvent()方法源码

 public boolean dispatchTouchEvent(MotionEvent event) {  
    /**
     * mOnTouchListener就是你在setOnTouchListener()中传递的listener
     * mViewFlags & ENABLED_MASK 意思是判断View控件是否enabled
     * mOnTouchListener.onTouch() 回调你传递的listener的onTouch方法(上面条件都为true才会执行)
     */
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { 
       return true; //true表示事件被这个View消费了
    }

    //这个onTouchEvent里有对onClick的处理,所以如果你给上面的onTouch()返回了true,那么onClick方法就不会被执行了  
    return onTouchEvent(event);
} 
 
 

第一个if判断中,只要设置了onTouchListener,且控件enable,且onTouch返回true,这个dispatchTouchEvent就返回true,

意味着这个控件触发并处理后续action事件(ACITON_UP,ACTION_MOVE等),不再往下分发事件;也不会执行它自己的onTouchEvent()。

我们来看View.onTouchEvent()的源码(简化):

public boolean onTouchEvent(MotionEvent event) {
        ...
       if ((viewFlags & ENABLED_MASK) == DISABLED) {
           if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
           }
           // A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.
           // 一个控件如果是disabled且clickable,仍然可以消费触摸事件,只是不去响应它
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
       }

      //判断控件是不是clickable
      if(((viewFlags & CLICKABLE) == CLICKABLE ||  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
           switch(event.getAction()) {
           case MotionEvent.ACTION_UP:
               ...
               ...
              if (!mHasPerformedLongPress) {
                   // This is a tap, so remove the longpress check
                   removeLongPressCallback();

                  // Only perform take click actions if we were in the pressed state</span>
                  if(!focusTaken) {
                     // Use a Runnable and post this rather than calling</span>
                     // performClick directly. This lets other visual state</span>
                     // of the view update before click actions start.</span>
                     if (mPerformClick == null {
                          mPerformClick = <span class="hljs-keyword">new</span> PerformClick();
                     }
                     if (!post(mPerformClick)) {
                          performClick();//控件被点击时,回调控件的onClickListener.onClick()
                     }
                  }
               }
               ...
               break;
           case MotionEvent.ACITON_DOWN:
               ...
               break;
           case MotionEvent.ACITON_CANCEL:
               ...
               break;
           case MotionEvent.ACTION_MOVE
               ...
               break;
           }
           return true; //可见,只要控件是clickable,就返回true,从而触发ACITON_U等后续事件
       }
  }

 
 
 
 

从第二个if语句可以看到,onTouchEvent会判断控件是不是clickable,只要控件是clickable就会进入到这个if判断的内部,然后不管是什么action,到最后都帮你返回true。

这样做有什么好处呢?我们看一看那个switch语句中的ACITON_UP分支,里面有一个performClick(),它专门用来处理控件的点击事件。我们知道,onToucEvent返回true时,dispatchTouchEvent也跟着返回true,从而触发后续的ACITON_UP,使得onTouchEvent会再一次被调用,然后才能进入到switch的ACITON_UP分支去处理该控件的点击事件。如果不帮你返回true,控件的onClick就永远没法被系统调用。

因此,只要控件是clickable的,控件的dispatchTouchEvent就一定返回true,从而消费事件。(提示:Button默认clickable,ImageView默认不是clickable)

PS:Android中只要view和layout调用了setOnClickListener(),那么它就会被设为clickable(参见下面的View.setOnClickListener()源码)

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


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;  

    //这里是ACITON_UP事件的处理逻辑,主要是遍历子view,调用子view的dispatchTouchEvent,看哪个返回true就记录它
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  //mMotionTarget用来记录View.dispathcTouchEvent返回true的子view
            mMotionTarget = null;  //重置目标view
        }          
         //判断有无拦截事件(disallowIntercept指是否允许事件被拦截,默认是false)
        if (disallowIntercept || !onInterceptTouchEvent(ev)) { //派发action_down的时候执行一次onInterceptTouchEvent
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  //子view的数量
            for (int i = count - 1; i >= 0; i--) {  //遍历当前ViewGroup的所有子view
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    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; 

                        //dispatchTouchEvent的返回值的意义体现在此!
                        if (child.dispatchTouchEvent(ev))  { 若child是个ViewGroup,正好递归调用ViewGroup.dispatchTouchEvent
                            mMotionTarget = child;  //记住dispatchTouchEvent返回true的子view
                            return true; //跳出循环,也就是不继续往下调用child.dispatchTouchEvent
                        }  
                    }  
                }  
            }  
        }  
    }  
    //判断是否为ACTION_UP或者ACTION_CANCEL</span><span></span></span>
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;  //将刚刚记录的子view赋值给target(最后一行,调用它的dispatchTouchEvent)    
    //target为null,即上面的mMotionTarget为null,意味着这个ViewGroup的子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;  
        }  
        return super.dispatchTouchEvent(ev); //若子view都不消费事件,就调用这ViewGroup的父类方法,即View.dispatchTouchEvent
    }  
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  //再一次执行onInterceptTouchEvent
        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); //调用记录的子viwe的dispatchTouchEvent  
} 

根据上面对源码的分析,可知ViewGroup.diaptchTouchEvent逻辑是这样的:

         用户触摸屏幕,首先产生的是ACTION_DOWN事件,if判断到当前是down事件,那就遍历ViewGroup中被触摸到的那些子ViewGoup和子View,调用它们的dispatchTouchEvent(如果是子ViewGroup,那不正好是递归嘛,想一想,这样最后肯定能把down事件传递给触摸到的所有viewGroup和view的dispatchTouchEvent,除非其中的某个ViewGroup使用onInterceptTouchEvent把事件拦截掉)。


举几个简单例子,加深理解


例子一:button的dispatchTouchEvent返回了true

     (什么时候返回true?控件setOnClickListener;或者setOnTouchListener,并在onTouch返回true且控件enable)

派发顺序:

    

分析:

1. 由于view消费了down事件,它就被记录了,下次传递move和up事件就找它了;

2. 包裹这个view的ViewGroup的onTouch和onTouchEvent将无法被执行


例子二: 某个ViewGroup的onTouchEvent返回true

派发顺序:

 事件分发机制_第2张图片     

分析:

1. 问:为什么down事件派发会走-②这么个顺序?

答:派发down事件,走到SecondLayout这一ViewGroup的dispatchTouchEvent时,会遍历SecondLayout的子view,发现都是返回false。所以这个ViewGroup.dispatchTouchEvent会继续走到执行后面代码,调用View.dispatchTouchEvent,而View.dispatchTouchEvent调用ViewGroup自身的onTouchEvent(没有复写的话从父类View继承)。这顺序正好对应箭头

同理,FirstLayout会等遍历完子view(SecondLayout和ThirdButton),发现它们的dispatchTouchEvent和onTouchEvent都返回false以后,执行后面代码,调用

View.dispatchTouchEvent,ViewGroup.onTouchEvent调用自身onTouchEvent,这个onTouchEvent被我们复写了,并返回true。这顺序对应箭头

2. 问:黄色路线是怎么回事?

答:派发down事件,其中的某个ViewGroup被记录了,派发后续事件,直接找到它,在ViewGroup.dispatchTouchEvent中直接调用自身的onTouchEvent。


例子三: 全返回false

派发顺序:

    

如果Activity里的view全都返回false,那么就调用它自身的onTouchEvent(再回到开头看看Activity.dispatchTouchEvent源码,一目了然)。

问:为什么Activity.onTouchEvent无论返回true还是false,都能消费事件?

答:因为Activity.dispatchTouchEvent必然至少被系统调用2次(down和up)


例子四:onInterceptTouchEvent返回true

派发顺序:

    事件分发机制_第3张图片

onInterceptTouchEvent返回true表示拦截事件,此时,ViewGroup.dispatchTouchEvent中的target为null,调用super.dispatchTouchEvent,即View.dispatchTouchEvent

 
 

 
 

 
 

你可能感兴趣的:(事件分发)