Android 记录2 对于Android事件分发的理解

来源:互联网 发布:linux装tensorflow 编辑:程序博客网 时间:2024/05/17 19:15

                                           Android事件分发的理解

         这两天一直在看关于Android Touch事件分发机制的一些文章,对于OnTouch,OnTouchEvent,dispatchTouchEvent,onInterceptTouchEvent等方法不是很理解,非常谢谢博主的文章 http://blog.csdn.net/guolin_blog/article/details/9097463,获益匪浅.

               故写下本博客记录一下自己的理解。

               View 包含:dispatchTouchEvent ,onTouch,onTouchEvent
               ViewGroup包含: :dispatchTouchEvent ,onTouch,onTouchEvent

               先来看看 View中dispatchTouchEvent方法的源码:
public boolean dispatchTouchEvent(MotionEvent event) {      if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&              mOnTouchListener.onTouch(this, event)) {          return true;      }      return onTouchEvent(event);  } 
              在这个方法中,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。
            也就是说如果OnTouch事件返回了true,就不会去执行OnTouchEvent,并且OnTouch要先于OnTouchEvent执行.
            那么问题来了,mOnTouchListener对象在哪里定义的呢?到底OnTouchEvent()函数到底做了什么?
             第一个问题:mOnTouchListener对象在哪里定义的呢?
public void setOnTouchListener(OnTouchListener l) {      mOnTouchListener = l;  } 
             可以看出这个是在我们对某一个view进行listener注册得来的,xxx.setOnTouchListener(). 然后我们可以重写onTouch函数来实现自己的功能.
如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
             第二个问题:view的OnTouchEvent()函数到底做了什么?源码:
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;          }      }      if (((viewFlags & CLICKABLE) == CLICKABLE ||              (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {          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();                              }                          }                      }                      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;          }          return true;      }      return false;  }
           第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的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方法。当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。
         还有一个很重要的知识点,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
         假设在OnTouch事件里返回了false或者没有注册touch事件,就一定会进入到OnTouchEvent方法中,如果组件默认能够点击,那么OnTouchEvent的第14行判断为true进入到判断内部,然后不管当前的action是什么,都会走到89行,返回一个true.如果组件默认不能点击,那么在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。


     再来看看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 || !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 (int i = count - 1; i >= 0; i--) {                  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;                          if (child.dispatchTouchEvent(ev))  {                              mMotionTarget = child;                              return true;                          }                      }                  }              }          }      }      boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||              (action == MotionEvent.ACTION_CANCEL);      if (isUpOrCancel) {          mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;      }      final View target = mMotionTarget;      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);      }      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);  }  

      首先在第13行可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。
     disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。
     那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?竟然就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。
     看看ViewGroup的onInterceptTouchEvent 这个方法的源码:
public boolean onInterceptTouchEvent(MotionEvent ev) {      return false;  } 
          可以看出,默认是返回false.因此就会跑到判断内部去执行.在第19行通过一个for循环,遍历了当前ViewGroup下的所有子View,然后在第24行判断当前遍历的View是不是正在点击的View,如果是的话就会进入到该条件判断的内部,然后在第29行调用了该View的dispatchTouchEvent.
    需要注意一下,调用子View的dispatchTouchEvent后是有返回值的。如果一个控件是可点击的,那么点击该控件时,dispatchTouchEvent的返回值必定是true(从最上面看 return OnTouchEvent(event))。因此会导致第29行的条件判断成立,于是在第31行给ViewGroup的dispatchTouchEvent方法直接返回了true。这样就导致后面的代码无法执行到了,就是表示view已经处理了该事件,viewGroup不需要处理.
     假设 不会在第31行返回true了,而是会继续执行后面的代码。那我们继续往后看,在第44行,如果target等于null,就会进入到该条件判断内部,这里一般情况下target都会是null,因此会在第50行调用super.dispatchTouchEvent(ev)。这句代码会调用到哪里呢?当然是View中的dispatchTouchEvent方法了,因为ViewGroup的父类就是View。

          

         现在整个ViewGroup的事件分发流程的分析大概就是这样了,简单梳理一下:

1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。

2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。

3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。


     


      今天就记录到这里,慢慢成长,多看大牛们的博客.



0 0