事件分发机制

来源:互联网 发布:nba火箭vs尼克斯数据 编辑:程序博客网 时间:2024/05/17 07:42

1.Android中事件分发
事件分发顺序:Activity(Window) -> ViewGroup -> View
1.1Activity的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {                    onUserInteraction();        }            //getWindow()获取的就是PhoneWindow对象        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    } //该方法为空方法 //当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法 //所以onUserInteraction()主要用于屏保 public void onUserInteraction() {   }

1.2 PhoneWindow的superDispatchTouchEvent(ev)

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

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

1.4 汇总;
当一个点击事件发生时,调用顺序如下:
- 事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 调用Window类实现类PhoneWindow的superDispatchTouchEvent()
- 调用DecorView的superDispatchTouchEvent()
- 最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的
dispatchTouchEvent()

1.5 回头看Activity的dispatchTouchEvent

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

小结:
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法 ,判断ViewGroup的dispatchTouchEvent()返回值是否为true,返回true就不执行Activity的onTouchEvent()方法;返回false,就执行。

2).View事件分发源码分析
2.1 View的dispatchTouchEvent()方法

//mOnTouchListener是在View类下setOnTouchListener方法里赋值的//mViewFlags & ENABLED_MASK) == ENABLED即当前点击的控件是否enable//mOnTouchListener的onTouch(this, event)方法的返回值是否为truepublic boolean dispatchTouchEvent(MotionEvent event) {      if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&              mOnTouchListener.onTouch(this, event)) {          return true;      }      return onTouchEvent(event);  public void setOnTouchListener(OnTouchListener l) { //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)    mOnTouchListener = l;  }  }  

第一个条件:mOnTouchListener != null;
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三个条件:mOnTouchListener.onTouch(this, event);
小结:
- 只有这三个条件都为true,dispatchTouchEvent返回true,不执行onTouchEvent(event),否则执行否则执行onTouchEvent(event)方法。
- onTouch()方法先于onTouchEvent(event)执行

2.2 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;          }      }       //如果该控件enable且可以点击的就会进入到下两行的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;                  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)) {              //请往下看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;  }  

由上可知

  • 在View为disable情况下,如果view可点击则onTouchEvent()返回true,否则onTouchEvent()返回false。
 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));      }  
  • 在View为enable情况下,如果view可点击则onTouchEvent()返回true,如果view不可点击,onTouchEvent()返回false.
public boolean onTouchEvent(MotionEvent event) {    if (((viewFlags & CLICKABLE) == CLICKABLE ||              (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {         //..........省略        //如果该控件enable且可以点击的,就一定会返回false        return true;      }  //如果该控件enable但不可以点击的,就一定会返回false    return false;  } 

结合以上2点可知:
只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.

2.3 View的点击事件在哪执行?
一次点击事件必不可少的行为MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP。那么点击事件一定是在MotionEvent.ACTION_UP中执行,看源码:

 switch (event.getAction()) {              case MotionEvent.ACTION_UP:                  boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                  if ((mPrivateFlags & PRESSED) != 0 || prepressed) {                 //.......省略......                    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) {                              if (mPerformClick == null) {                                  mPerformClick = new PerformClick();                              }                              if (!post(mPerformClick)) {              //performClick翻译一下,执行点击? 呵呵                                performClick();                              }                          }                      }               //.......省略......                break;                  }             //.......省略......                }

2.3.1 performClick()源码

public boolean performClick() {      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);      //只要mOnClickListener不为null,就会去调用mOnClickListener的onClick方法    if (mOnClickListener != null) {          playSoundEffect(SoundEffectConstants.CLICK);          mOnClickListener.onClick(this);          return true;      }      return false;  }  public void setOnClickListener(OnClickListener l) {      if (!isClickable()) {          setClickable(true);      }      mOnClickListener = l;  } 

总结:

  • 执行顺序:onTouch()–>onTouchEvent()–>onClick()
  • view为disable时,onTouch()不会执行,onTouchEvent()执行,onClick()不执行
  • 如果view为enable且onTouch()返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),onClick()也不会执行。
  • 如果view为enable且onTouch()返回false,那么就会执行onTouchEvent();执行onClick()。
  • 只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.

3.ViewGroup事件分发源码分析
3.1 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;          }  //如果我们在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;                      //条件判断的内部调用了该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;  //没有任何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);  }  

3.1.1 disallowIntercept
disallowIntercept:是否禁用事件拦截的功能(默认是false),可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。

3.1.2 onInterceptTouchEvent(ev) 是否拦截事件(默认是false)。

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

3.2 MotionEvent.ACTION_DOWN时,做了些什么??

  if (action == MotionEvent.ACTION_DOWN) {          if (mMotionTarget != null) {              mMotionTarget = null;          }  //onInterceptTouchEvent()返回false或disallowIntercept==true进入if语句        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;            //条件判断的内部调用了该View的dispatchTouchEvent()方法                          if (child.dispatchTouchEvent(ev))  {         //如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true        //因此会导致条件判断成立                            mMotionTarget = child;          //于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出        //即把ViewGroup的touch事件拦截掉                            return true;                          }                      }                  }              }          }      }  

首先判断(disallowIntercept || !onInterceptTouchEvent(ev))是否为true,true则进入if语句,通过for循环,遍历了当前ViewGroup下的所有子View,查找点击的View,

  • 找到则调用该View的dispatchTouchEvent()方法,返回值为true,则该View消耗了DOWN事件,ViewGroup的dispatchTouchEvent()结束,后续的MOVE等事件都由该View消费。
  • 没找到或找到但View的dispatchTouchEvent()返回值为false,则DOWN事件未被任何View消耗。走流程3.3。

3.3 没有任何View消耗事件的情况或onInterceptTouchEvent()为true

  //DOWN事件未被任何View消耗则mMotionTarget==null;  final View target = mMotionTarget;  //没有任何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);      }  

由上可知:
没有任何View消耗DOWN事件或onInterceptTouchEvent()为true情况下,target == null,会调用ViewGroup父类View的dispatchTouchEvent(),会执行ViewGroup的onTouch()、onTouchEvent()。

  • super.dispatchTouchEvent(ev)为true,消耗事件。
  • super.dispatchTouchEvent(ev)为false,事件向上传递。

3.4 拦截子View的DOWN事件
onInterceptTouchEvent()设置为true,走流程3.3,如果DOWN事件被ViewGroup消费了,该事件列的其他事件(Move、Up)将直接传递给ViewGroup的onTouch()或onTouchEvent()。

3.5 不拦截子View的DOWN事件,拦截子View的其他事件
子View消费了DOWN事件,在ViewGroup的onInterceptTouchEvent方法返回true拦截该MOVE事件,这个MOVE事件将会被系统变成一个CANCEL事件传递给子View的dispatchTouchEvent()方法,后续又来了一个MOVE事件,该MOVE事件会直接传递给ViewGroup的onTouch()或onTouchEvent().

//子View消费了DOWN事件且onInterceptTouchEvent()==true&&disallowIntercept ==false  if (!disallowIntercept && onInterceptTouchEvent(ev)) {          final float xc = scrolledXFloat - (float) target.mLeft;          final float yc = scrolledYFloat - (float) target.mTop;          mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;          //设置MotionEvent.ACTION_CANCEL事件        ev.setAction(MotionEvent.ACTION_CANCEL);          ev.setLocation(xc, yc);     //调用子view的dispatchTouchEvent()并设置mMotionTarget==null   //下个事件来,走流程3.3        if (!target.dispatchTouchEvent(ev)) {          }          mMotionTarget = null;        //MotionEvent.ACTION_CANCEL事件被消费          return true;      }  

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

  • 当(Activity、ViewGroup、View)的dispatchTouchEvent在进行事件分发的时候,只有前一个事件返回true,才会收到后一个事件。
  • 如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()收到ACTION_DOWN的函数并返回true,那么也能收到ACTION_MOVE和ACTION_UP 。

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

这里写图片描述
- 如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费了DOWN事件,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给这个View的onTouchEvent()并结束本次事件传递过程。这里写图片描述
参考

原创粉丝点击